# Migrate from Ronin VRF to Randoo

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 mainnet | Saigon |
| --- | --- | --- |
| Randoo coordinator | `0xC66eB2e7EE91145875000Ad46B55600000000001` | `0xC66eB2e7EE91145875000Ad46B55600000000001` |
| Chain ID | `2020` | `202601` |
| Implementation | `0xA6A65cce89a06bD9664d2A6cd226454e3D5fC4bd` | `0x4b06EDE3b7921921f11Fb34BAA740ba37Bee3263` |
| Default public RPC | `https://api.roninchain.com/rpc` | `https://saigon-testnet.roninchain.com/rpc` |
| Other RPC providers | [https://docs.roninchain.com/developers/tools/node-providers](https://docs.roninchain.com/developers/tools/node-providers) | same list |
| Native Ronin VRF (the address you replace) | `0x16A62a921e7fEC5Bf867fF5c805b662Db757B778` | `0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312` |

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:

```sh
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 that network's implementation. 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.

```sh
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 found | Action |
| --- | --- |
| Constructor argument, deploy script, env variable, or config value | Edit (step 4) |
| `VRFConsumerUpgradeable`: address in proxy storage, changed via `_setVRFCoordinator` through an upgrade or admin path | STOP. 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 consumer | List all of them and ASK A2 before touching more than one |
| An address in comments, README, or prose that differs from `randoo.json` | Flag 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.

```diff
- 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.

```diff
- (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 ABIs 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:

```sh
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
```

4. 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):

```sh
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.

```markdown
# 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 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
```

_Last verified 2026-08-25._
