# Migrate from Ronin VRF

Ronin VRF is the on-chain verifiable random function used by Ronin games: a consumer calls `requestRandomSeed` on a coordinator and receives a proven seed in `rawFulfillRandomSeed`. Native Ronin VRF is the [Sky Mavis coordinator](https://docs.roninchain.com/developers/tools/vrf). Randoo is a separate drop-in with the same consumer interface.

Your consumer already works with Randoo. It is the same `IRoninVRFCoordinatorForConsumers` interface (`requestRandomSeed`, `estimateRequestRandomFee`, `rawFulfillRandomSeed`), so the migration is one address:

```solidity The whole migration
// before: native Ronin VRF
new LootBox(0x16A62a921e7fEC5Bf867fF5c805b662Db757B778); // mainnet
new LootBox(0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312); // Saigon

// after: Randoo (same address on mainnet AND Saigon)
new LootBox(0xC66eB2e7EE91145875000Ad46B55600000000001);
```

No code changes, no re-audit of your consumer, no new SDK. Deploy your existing `VRFConsumer` with the Randoo coordinator address and requests flow the same way, for half the service fee ($0.005 vs $0.01), quoted by the same `estimateRequestRandomFee` call.

## Consumer · unchanged

The published sample is `TemplateConsumer`; its base `RandooConsumerRonin` ships with the sample and is inlined first. It has the same shape as the native `VRFConsumer` base, so an existing consumer keeps its code and only changes the coordinator address at deploy.

```solidity RandooConsumerRonin.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IRoninVRFCoordinatorForConsumers} from "./interfaces/IRoninVRFCoordinatorForConsumers.sol";

/// @notice Ronin VRFConsumer-shaped base. Swap coordinator address to migrate.
abstract contract RandooConsumerRonin {
    error OnlyCoordinatorCanFulfill();

    address public vrfCoordinator;

    constructor(address vrfCoordinator_) {
        vrfCoordinator = vrfCoordinator_;
    }

    function rawFulfillRandomSeed(bytes32 reqHash, uint256 randomSeed) external {
        if (msg.sender != vrfCoordinator) revert OnlyCoordinatorCanFulfill();
        _fulfillRandomSeed(reqHash, randomSeed);
    }

    function _fulfillRandomSeed(bytes32 reqHash, uint256 randomSeed) internal virtual;

    function _requestRandomness(
        uint256 value,
        uint256 callbackGasLimit,
        uint256 gasPriceToFulfill,
        address refundAddr
    ) internal returns (bytes32 reqHash) {
        reqHash = IRoninVRFCoordinatorForConsumers(vrfCoordinator).requestRandomSeed{value: value}(
            callbackGasLimit, gasPriceToFulfill, address(this), refundAddr
        );
    }
}
```

```solidity TemplateConsumer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// ---------------------------------------------------------------------------
// Randoo consumer template: copy-pasteable as a unit with
// RandooConsumerTemplate.t.sol and MockRandooCoordinator.sol (this folder), plus
// src/RandooConsumerRonin.sol and src/interfaces/IRoninVRFCoordinatorForConsumers.sol.
// Imports resolve in a fresh `forge init` with only forge-std installed.
// ---------------------------------------------------------------------------

import {RandooConsumerRonin} from "../../src/RandooConsumerRonin.sol";

/// @notice The canonical Randoo consumer. Copy it, rename it, put your game
///         logic in `_fulfillRandomSeed`.
///
/// Flow: a caller pays `estimateRequestRandomFee(callbackGasLimit, gasPrice)`
/// into `roll`, which forwards the request to the coordinator and returns the
/// `reqHash`. Later the coordinator calls `rawFulfillRandomSeed` (inherited,
/// coordinator-only) which lands in `_fulfillRandomSeed` with the seed.
contract TemplateConsumer is RandooConsumerRonin {
    /// @notice Last seed delivered by the coordinator.
    uint256 public lastSeed;
    /// @notice Request hash of the most recent `roll`, then of the most recent fulfil.
    bytes32 public lastReqHash;

    constructor(address vrfCoordinator_) RandooConsumerRonin(vrfCoordinator_) {}

    /// @param callbackGasLimit Gas the coordinator will give `_fulfillRandomSeed` (50k..2.5M).
    /// @param gasPrice Gas price the fulfiller may spend; must be >= the coordinator floor.
    /// @param refundAddr Receives unspent fulfilment gas; must be non-zero.
    function roll(uint256 callbackGasLimit, uint256 gasPrice, address refundAddr)
        external
        payable
        returns (bytes32 reqHash)
    {
        reqHash = _requestRandomness(msg.value, callbackGasLimit, gasPrice, refundAddr);
        lastReqHash = reqHash;
    }

    /// @dev Your randomness lands here. Keep it within `callbackGasLimit`.
    function _fulfillRandomSeed(bytes32 reqHash, uint256 randomSeed) internal override {
        lastReqHash = reqHash;
        lastSeed = randomSeed;
    }
}
```

## What stays the same

- The consumer base contract and callback signature.
- Pay-per-request in RON with msg.value; leftover refunded.
- Billing at your declared gasPrice (plus a 10% buffer applied at request time), never the fulfiller's actual tx fee. Recommended: `20e9 + block.basefee * 2`.
- Proofs verified on-chain by the same audited Chainlink VRF verifier.

## What is different under the hood

- Half the service fee: $0.005 per request instead of $0.01.
- One coordinator address on both networks, so configs and scripts need no per-chain address table.
- Different seed derivation (bound to the request blockhash): seeds are not comparable across the two coordinators, only their guarantees are.
- If your consumer is not payable, set `refundAddress` to an EOA.

The full caveat list lives in the migration playbook: [/docs/migrate-from-ronin-vrf.md](/docs/migrate-from-ronin-vrf.md).

_Last verified 2026-08-25._
