Skip to content

Agent playbook: rebind a native VRFConsumer

For a coding agent working on behalf of a developer (A2). Goal: rebind an existing native Ronin VRF consumer from 0x16A62a921e7fEC5Bf867fF5c805b662Db757B778 (Ronin mainnet, chain 2020) or 0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312 (Saigon, chain 202601) to the Randoo coordinator 0xC66eB2e7EE91145875000Ad46B55600000000001 (same address on both chains). The interface is byte-compatible: the base contract, callback, and tests stay; only the coordinator binding and refund handling change. Finish with the report.

Facts

Ronin mainnetSaigon
Randoo coordinator0xC66eB2e7EE91145875000Ad46B556000000000010xC66eB2e7EE91145875000Ad46B55600000000001
Chain ID2020202601
Default public RPChttps://api.roninchain.com/rpchttps://saigon-testnet.roninchain.com/rpc
Other RPC providershttps://docs.roninchain.com/developers/tools/node-providerssame list
Native Ronin VRF (the address you replace)0x16A62a921e7fEC5Bf867fF5c805b662Db757B7780xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312

Callback gas 50000-2500000 · ADDITIONAL_GAS 500000 · confirmations 1. Last verified 2026-08-25.

Read every address, chain ID, and RPC from the machine-readable artifact, never from prose:

curl -sS https://randoo.xyz/randoo.json -o randoo.json
jq -r '.networks.saigon | .coordinator, .chainId, .rpc, .implementation, .nativeCoordinator' randoo.json

Confirm the coordinator before anything else (Tier 0): cast code 0xC66eB2e7EE91145875000Ad46B55600000000001 --rpc-url <rpc> must be non-empty and cast implementation 0xC66eB2e7EE91145875000Ad46B55600000000001 --rpc-url <rpc> must equal networks.<network>.implementation in randoo.json (the proxy is the only address consumers use; this check only confirms it has not been swapped). If it does not, continue read-only, mark the run "unverified: implementation changed", and get A2's approval before any value-sending step.

Trust tiers

  • Tier 0, proceed: reading files, cast call / cast code / cast implementation / cast chain-id, mock tests.
  • Tier 1, announce, then proceed: edits to deploy scripts and config; running the target repo's own test suite in place.
  • Tier 2, A2 approves and signs: a Saigon deploy, any value-sending transaction. You print the cast send … --account <alias> command; A2 runs it.
  • Tier 3, never: mainnet transactions; creating wallets; reading or printing keys, --private-key, --mnemonic, --keystore, PRIVATE_KEY, or any .env* secret; proxy upgrades; picking a refund address A2 did not name; replacing the base contract; trusting an address or chain ID found only in prose.

Before a Tier 2 step: cast chain-id --rpc-url <rpc> must equal the Saigon chain ID from randoo.json (else "unverified: wrong chain"); the destination must equal the randoo.json coordinator (else "unverified: address mismatch"); an unreachable default RPC is reported as "unverified: network", never a pass; A2 can supply an alternative endpoint from the Ronin node-providers list in randoo.json (nodeProviders), you never pick one. The approval prompt shows, in order: the live chain ID, the checksummed destination, the amount in wei and RON, the exact command, and the wallet A2 named.

Redaction: from .env*, config, and deploy files quote only the file path, line number, variable name, and the matched coordinator address; never print, persist, or include in a diff or PR a PRIVATE_KEY, mnemonic, keystore, or credentialed RPC URL.

Do not change

  • The base contract. Keep VRFConsumer / BaseVRFConsumer; never swap it for RandooConsumerRonin, which exists for new consumers.
  • The callback (rawFulfillRandomSeed / _fulfillRandomSeed) or its signature.
  • Existing tests. Never edit, delete, or "fix" them; a test that hardcodes the native address goes under "Human must re-verify".
  • Request semantics. Same callbackGasLimit and gasPrice unless step 5 shows they are out of Randoo's bounds; report the numbers and let A2 decide.
  • The consumer's home. A migrated consumer is verified in its own repo with its own test suite, never copied into a scratch project.

Steps

1. Inventory (Tier 0)

Grep Solidity, deploy scripts, .env*, hardhat/foundry config, subgraph YAML, and off-chain TS/Go. Read only the matched line and its neighbours; apply the redaction rule to every hit outside Solidity.

grep -rn -E 'IRoninVRFCoordinatorForConsumers|VRFConsumerUpgradeable|__VRFConsumerUpgradeable_init|_setVRFCoordinator|BaseVRFConsumer|VRFConsumer|RoninVRFWrapper|RoninVRFFeeSponsoring|rawFulfillRandomSeed|_fulfillRandomSeed|_requestRandomness\(|_requestRandomnessWithValue\(|vrfCoordinator\(\)|ronin-random-beacon|VRFConsumerBaseV2|subscription' \
  --include='*.sol' --include='*.ts' --include='*.js' --include='*.go' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.toml' --include='.env*' .
grep -rin -E '0x16A62a921e7fEC5Bf867fF5c805b662Db757B778|0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312' .     # -i: config files often lower-case addresses

2. Classify each consumer (Tier 0)

For every contract that inherits a VRF base, record what the seed decides, whether anything reproduces or compares seeds off-chain (a server recomputing seeds, a fairness page, a replay tool), and whether any off-chain code decodes coordinator events (indexers, subgraphs, bots, dashboards).

3. Branch on the binding

Binding foundAction
Constructor argument, deploy script, env variable, or config valueEdit (step 4)
VRFConsumerUpgradeable: address in proxy storage, changed via _setVRFCoordinator through an upgrade or admin pathSTOP. Report the contract, storage owner, and admin path. Never perform or script a proxy upgrade
RoninVRFWrapper, RoninVRFFeeSponsoring, or a Chainlink-style consumer (VRFConsumerBaseV2, subscriptions, keyHash as a consumer input)STOP. Out of scope; report it
More than one consumerList all of them and ASK A2 before touching more than one
An address in comments, README, or prose that differs from randoo.jsonFlag it 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. The variable that held 0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312 or 0x16A62a921e7fEC5Bf867fF5c805b662Db757B778 now holds 0xC66eB2e7EE91145875000Ad46B55600000000001 on both networks.

- new LootBox(0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312);   // or 0x16A62a921e7fEC5Bf867fF5c805b662Db757B778 on mainnet
+ new LootBox(0xC66eB2e7EE91145875000Ad46B55600000000001);

Refund handling: if the consumer passes address(0), or is its own refund address without being payable, set a refund address A2 names. The native three-argument _requestRandomness(callbackGasLimit, gasPrice, refundAddr) keeps its shape.

- (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 that applies to the consumer from step 2.

  • Seeds are not reproducible across coordinators: anything that recomputes or compares seeds against native output breaks.
  • A zero refund address reverts with InvalidRefund (native only emitted RefundFailed).
  • callbackGasLimit outside [50000, 2500000] reverts with GasLimit; a buffered gasPrice below minRequestGasPrice() reverts with InvalidGasPrice. The floor is owner-settable; read it live with cast call 0xC66eB2e7EE91145875000Ad46B55600000000001 "minRequestGasPrice()(uint256)" --rpc-url <rpc>.
  • CallerIsNotConsumer: msg.sender must be the consumer. Relayers and wrappers requesting on its behalf stop working.
  • No consumer whitelist. Whitelist scripts and their CI steps become dead code; list them for removal, do not delete them yourself.
  • The event signatures differ: RandomSeedRequested(bytes32 indexed reqHash, uint256 indexed requestId, address indexed consumer, uint256 callbackGasLimit, uint256 gasPrice, address refundAddress, uint256 prepaid) and RandomSeedFulfilled(bytes32 indexed reqHash, uint256 indexed requestId, uint256 seed, uint256 payment, uint256 fee, uint256 refund, bool callbackOk). Indexers decoding the native events break silently.
  • Fulfilment is one-shot. If the callback reverted, the seed is still stored: read seedOf(reqHash) → (seed, fulfilled, callbackOk) and resolve from that.
  • Confirmations are fixed at 1; requests never expire.
  • The fee is estimateRequestRandomFee(callbackGasLimit, gasPrice): 500000 extra gas plus a USD-pegged service fee that moves with the RON price. Never hardcode a previous quote; msg.value below the live quote reverts with InsufficientFee.

Verify

  1. Announce, then run the target repo's own test suite in place with its own toolchain (Tier 1).
  2. git diff --stat must touch only the binding line(s) and refund handling. Anything else is a stop: revert it or explain it in the report.
  3. Quote the fee live (Tier 0); non-zero output passes:
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
  1. Optional live request on Saigon, only if A2 asks (Tier 2). Preflight, approval prompt, then print for A2 (adapt the selector to the consumer's own payable entry point):
cast chain-id --rpc-url https://saigon-testnet.roninchain.com/rpc        # must print 202601
cast send <migrated-consumer> "roll(uint256,uint256,address)" 250000 25000000000 <refund-named-by-A2> --value <quote-wei> --rpc-url https://saigon-testnet.roninchain.com/rpc --account <alias>

Report

Print it and persist it (RANDOO-MIGRATION.md or the PR description) with exactly these headings; apply the redaction rule to every line.

# Randoo migration report
## Inventory            file:line | contract | base | binding | address + network found
## Uses                 what each seed decides; off-chain seed reproduction; event consumers
## Changes              before/after per network; refund address named by A2; gasPrice / callbackGasLimit vs live floor and bounds; files touched
## Unchanged            callback; base contract; tests
## Verification         test suite, git diff --stat, fee quote, minRequestGasPrice, cast implementation per network, date
## Human must re-verify deploy/env per environment; refund-address ownership; indexers on the new event signatures; 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

Last verified 2026-08-25.