Blockchain / Global / Address

Contract

0xD8f7DF0aBD834eDcb3e4FC83A11175eB77449f3B

Balance
0 SOMI
Transactions sent
1
Type
Contract · 12,543 bytes of code

Booloot Verified

Compiler
solc 0.8.30+commit.73712a01
Language
Solidity
Optimization
No
EVM version
—
License
—
Verified
2026-10-12

Booloot.sol

and 10 more files

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

// Chainlink VRF V2.5 Imports (Direct Funding, Native Token Payment)
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
import {VRFV2PlusWrapperConsumerBase} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapperConsumerBase.sol"; // For Direct Funding
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";

/**
 * @title Booloot Game Contract - Secure VRF Only
 * @author Aratta Labs
 * @notice A mystery box game utilizing Chainlink VRF for secure, verifiable randomness. Winnings are stored for players to claim manually.
 * @dev The contract exclusively uses the secure 'play' function. The 'rng' function remains for potential off-chain testing/simulation but is not used internally for prize selection.
 * @custom:version 9
 * @custom:emoji 📊
 * @custom:security-contact atenyun@gmail.com
 */
contract Booloot is Pausable, ReentrancyGuard, VRFV2PlusWrapperConsumerBase, ConfirmedOwner {
    // =============================
    //          Custom Errors
    // =============================

    /// @dev Thrown when the payment sent is less than the required game fee plus VRF fee.
    error InsufficientPayment(uint256 providedAmount, uint256 requiredFee);
    /// @dev Thrown if an internal error occurs when trying to select a prize index. (Legacy error)
    error PrizeNotFound(uint256 attempts);
    /// @dev Thrown when a VRF request is made while a previous one is still pending fulfillment.
    error RequestAlreadyPending();
    /// @dev Thrown if the VRF fulfillment callback returns an unexpected Request ID.
    error VRFRequestMismatch(uint256 providedRequestId);
    /// @dev Thrown if the contract's current ETH balance cannot cover the maximum possible prize plus the game fee.
    error InsufficientContractBalance(uint256 required, uint256 current);

    // =============================
    //             Events
    // =============================

    /// @notice Emitted when a player successfully wins and receives a prize.
    /// @param player The address of the player who won.
    /// @param prizeAmount The amount of the prize won in wei.
    /// @param timestamp The time the event occurred.
    event Win(address indexed player, uint256 prizeAmount, uint256 timestamp);
    /// @notice Emitted when the owner successfully withdraws funds from the contract.
    /// @param recipient The address that received the withdrawn funds (contract owner).
    /// @param amount The amount of ETH withdrawn in wei.
    /// @param timestamp The time the event occurred.
    event Withdrawal(address indexed recipient, uint256 amount, uint256 timestamp);
    /// @notice Emitted when the contract owner updates the available prize pool array.
    /// @param newPrizes The array of new prize amounts.
    /// @param timestamp The time the event occurred.
    event PrizesUpdated(uint256[] newPrizes, uint256 timestamp);

    // VRF Events
    /// @notice Emitted immediately after a VRF request is successfully submitted to the Chainlink network.
    /// @param requestId The unique ID assigned to the randomness request.
    /// @param requester The address of the player who paid for and initiated the request.
    /// @param paidAmount The exact amount of native token (STT) paid to the VRF Wrapper.
    event VRFRandomnessRequested(uint256 indexed requestId, address indexed requester, uint256 paidAmount);
    /// @notice Emitted when the VRF callback successfully returns the random number and the player is paid.
    /// @param requestId The unique ID of the request that was fulfilled.
    /// @param prizeAmount The final prize amount awarded to the player.
    event VRFRandomnessFulfilled(uint256 indexed requestId, uint256 prizeAmount);

    // =============================
    //         State Variables
    // =============================

    /// @notice The fixed maximum prize amount (4 ETH), used for ensuring sufficient contract reserve before a game starts.
    uint256 public constant MAX_PRIZE_RESERVE_ETH = 4 ether;

    /// @notice The fixed fee (in wei) required from the player to play a single round of the game.
    uint256 public fee = 1 ether;

    /// @notice An array of fixed prizes available in the game, in wei. This pool is infinitely reusable.
    uint256[] public prizes = [
        0.1 ether,
        0.1 ether,
        0.1 ether,
        0.15 ether,
        0.2 ether,
        0.2 ether,
        0.25 ether,
        0.25 ether,
        0.3 ether,
        0.4 ether,
        0.4 ether,
        0.5 ether,
        0.5 ether,
        0.6 ether,
        0.7 ether,
        0.8 ether,
        0.9 ether,
        1.1 ether,
        1.2 ether,
        1.5 ether,
        2 ether,
        2.5 ether,
        3.1 ether,
        3.3 ether,
        4 ether
    ];

    /// @notice The total number of unique players who have participated in the game.
    uint256 public uniquePlayerCount;

    /// @notice A mapping from player addresses to their total accumulated winnings (in wei).
    mapping(address => uint256) public wins;

    /// @notice A mapping from player addresses to the total number of games they have played.
    mapping(address => uint256) public players;

    /// @notice An array containing the addresses of all unique players who have played.
    address[] public allPlayers;

    /// @notice A mapping to quickly check if a player's address is already registered in the `allPlayers` array.
    mapping(address => bool) public isPlayerRegistered;

    /// @notice A mapping from player addresses to the total accumulated winnings (prize + refund) they can claim.
    mapping(address => uint256) public unclaimedWinnings;

    // Structs
    /// @notice Data structure to represent a player's statistics for leaderboard purposes.
    struct PlayerData {
        /// @notice The player's unique wallet address.
        address player;
        /// @notice The total accumulated winnings (in wei) of the player.
        uint256 wins;
        /// @notice The total number of times the player has played the game.
        uint256 played;
    }

    // =============================
    //         Chainlink VRF V2+
    // =============================

    /// @notice The maximum gas limit the VRF Wrapper can use when calling `fulfillRandomWords`.
    uint32 public constant VRF_CALLBACK_GAS_LIMIT = 2_100_000;
    /// @notice The number of block confirmations the oracle network waits for before fulfilling the request.
    uint16 public constant VRF_REQUEST_CONFIRMATIONS = 3;
    /// @notice The number of random words requested from VRF. One word is used for the prize index.
    uint32 public constant VRF_NUM_WORDS = 3;

    /// @notice The Request ID of the most recent secure VRF randomness request.
    uint256 public latestRequestId;

    /// @notice Status flag indicating if the latest randomness request has been fulfilled.
    bool public fulfilled = true;

    /// @notice Maps a VRF request ID to the address of the player who made the request.
    mapping(uint256 => address) public requestToPlayer;

    /// @notice Maps a VRF request ID to the overpayment refund amount owed to the player.
    mapping(uint256 => uint256) public requestRefunds;

    /// @notice Stores the latest random word(s) received from the Chainlink VRF fulfillment callback.
    uint256[] public latestRandomWord;

    // =============================
    //         Constructor
    // =============================

    /**
     * @notice Initializes the contract with the VRF V2+ Wrapper address.
     * @param wrapper The address of the Protofire Chainlink VRF V2+ Wrapper contract.
     */
    constructor(address wrapper) payable ConfirmedOwner(msg.sender) VRFV2PlusWrapperConsumerBase(wrapper) {}

    // =============================
    //          Management
    // =============================

    /**
     * @notice Updates the fixed fee required to play the game.
     * @dev Only the contract owner can call this function.
     * @param _fee The new fee amount in wei.
     */
    function updateFee(uint256 _fee) public onlyOwner {
        fee = _fee;
    }

    /**
     * @notice Updates the array of fixed prizes for the game.
     * @dev Only the contract owner can call this function.
     * @param _prizes An array containing the new prize amounts in wei.
     */
    function updatePrizes(uint256[] memory _prizes) public onlyOwner nonReentrant {
        prizes = _prizes;
        emit PrizesUpdated(_prizes, block.timestamp);
    }

    // =============================
    //            Views
    // =============================

    /**
     * @notice Retrieves the current price required to pay the Chainlink VRF oracle.
     * @dev This should be used to calculate the total required payment (game fee + VRF fee) for `play()`.
     * @return The required VRF price in native token (STT in Somnia).
     */
    function getVRFRequestPrice() public view returns (uint256) {
        return i_vrfV2PlusWrapper.calculateRequestPriceNative(VRF_CALLBACK_GAS_LIMIT, VRF_NUM_WORDS);
    }

    /**
     * @notice Retrieves the entire array of fixed prizes currently available in the pool.
     * @return An array containing all prize amounts in wei.
     */
    function getAllPrizes() external view returns (uint256[] memory) {
        return prizes;
    }

    /**
     * @notice Retrieves a paginated batch of player addresses and their statistics for leaderboard construction.
     * @param startIndex The starting index for the batch.
     * @param batchSize The number of players to return in the batch.
     * @return A memory array containing the PlayerData struct for the requested batch.
     */
    function getPlayersBatch(uint256 startIndex, uint256 batchSize) external view returns (PlayerData[] memory) {
        uint256 endIndex = startIndex + batchSize;
        if (endIndex > allPlayers.length) endIndex = allPlayers.length;

        uint256 resultLength = endIndex - startIndex;
        PlayerData[] memory batch = new PlayerData[](resultLength);

        for (uint256 i = startIndex; i < endIndex; i++) {
            batch[i - startIndex] = PlayerData({player: allPlayers[i], wins: wins[allPlayers[i]], played: players[allPlayers[i]]});
        }
        return batch;
    }

    /**
     * @notice Generates a predictable number based on block data, sender address, and a nonce.
     * @dev CRITICAL VULNERABILITY: This function remains for testing/simulation. It is highly susceptible to miner manipulation and front-running. DO NOT USE INTERNALLY FOR PRIZES.
     * @param len The upper bound (non-inclusive) for the random number (e.g., prizes.length).
     * @param nonce An additional value to prevent simple replay attacks.
     * @return The pseudo-random number, which can be manipulated.
     */
    function rng(uint256 len, uint256 nonce) public view returns (uint256) {
        return uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, nonce))) % len;
    }

    /**
     * @notice Returns the modulo of a random word by the prizes length. Used for testing the prize selection logic.
     * @param randomWords A simulated random word.
     * @return The index into the prize array.
     */
    function testTest(uint256 randomWords) public view returns (uint256) {
        return randomWords % prizes.length;
    }

    // =============================
    //         Game Logic (Secure VRF)
    // =============================

    /**
     * @notice Returns the status of the latest secure VRF request.
     * @return requestId The unique ID of the last request.
     * @return isPending True if the request has been submitted but not yet fulfilled.
     * @return isFulfilled True if the last request has successfully been fulfilled.
     */
    function getRequestStatus() external view returns (uint256 requestId, bool isPending, bool isFulfilled) {
        return (latestRequestId, latestRequestId != 0 && !fulfilled, fulfilled);
    }

    /**
     * @notice Returns the random word(s) from the most recently fulfilled VRF request.
     * @dev Requires that a request has already been successfully fulfilled (`fulfilled == true`).
     * @return An array containing the random word(s).
     */
    function getLatestRandomWord() external view returns (uint256[] memory) {
        require(fulfilled, "No fulfilled request yet");
        return latestRandomWord;
    }

    /**
     * @notice Requests a secure, verifiable random number from Chainlink VRF.
     * @dev Player must pay the game fee PLUS the current VRF fee. This function initiates an asynchronous process. The prize is awarded in `fulfillRandomWords`.
     * @return The request ID for tracking the randomness fulfillment.
     */
    function play() external payable nonReentrant whenNotPaused returns (uint256) {
        address player = _msgSender();

        // Enforce financial reserve check
        uint256 requiredReserve = MAX_PRIZE_RESERVE_ETH + fee;
        if (address(this).balance < requiredReserve) {
            revert InsufficientContractBalance(requiredReserve, address(this).balance);
        }

        // 1. Block overlapping VRF requests
        if (latestRequestId != 0 && !fulfilled) revert RequestAlreadyPending();

        // 2. Compute total required fee (Game Fee + VRF Fee)
        uint256 vrfPrice = getVRFRequestPrice();
        uint256 totalRequiredPayment = fee + vrfPrice;

        // 3. Check for sufficient payment
        if (msg.value < totalRequiredPayment) {
            revert InsufficientPayment(msg.value, totalRequiredPayment);
        }

        // 4. Calculate overpayment refund amount (if any)
        uint256 overpaymentRefund = msg.value - totalRequiredPayment;

        // 5. Signal native payment to the wrapper
        VRFV2PlusClient.ExtraArgsV1 memory extraArgs = VRFV2PlusClient.ExtraArgsV1({nativePayment: true});
        bytes memory args = VRFV2PlusClient._argsToBytes(extraArgs);

        // 6. Submit request (uses native STT)
        (uint256 requestId, uint256 paid) = requestRandomnessPayInNative(VRF_CALLBACK_GAS_LIMIT, VRF_REQUEST_CONFIRMATIONS, VRF_NUM_WORDS, args);

        // 7. Update state for request tracking - REFUND STORAGE RESTORED
        latestRequestId = requestId;
        fulfilled = false;
        requestToPlayer[requestId] = player;
        requestRefunds[requestId] = overpaymentRefund; // Store refund to be processed after prize is awarded

        players[player] += 1;

        if (!isPlayerRegistered[player]) {
            allPlayers.push(player);
            isPlayerRegistered[player] = true;
            uniquePlayerCount++;
        }

        emit VRFRandomnessRequested(requestId, _msgSender(), paid);

        return requestId;
    }

    /**
     * @notice Called by the VRF Wrapper after the random word is generated.
     * @dev This is the crucial callback function that verifies the VRF result, selects the prize, pays the player, updates stats, and saves winnings for later claiming.
     * @dev Note: https://docs.chain.link/vrf/v2-5/security#fulfillrandomwords-must-not-revert:~:text=If%20your%20fulfillRandomWords,Automation%20Node.
     * @param requestId The ID of the VRF request that was fulfilled.
     * @param randomWords The array of random numbers (must be length of VRF_NUM_WORDS).
     */
    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        // 1. Validation (Cleaned up and precise validation restored)
        require(randomWords.length == VRF_NUM_WORDS, "Incorrect word count");
        require(requestId == latestRequestId, "VRFRequestMismatch");

        address player = requestToPlayer[requestId];
        uint256 overpaymentRefund = requestRefunds[requestId];

        // 2. Select prize using the random word
        uint256 prizeIndex = randomWords[0] % prizes.length;
        uint256 prizeAmount = prizes[prizeIndex];

        // 3. Instead of immediate transfer, add prize and refund to unclaimed winnings
        uint256 totalPayout = prizeAmount + overpaymentRefund;
        unclaimedWinnings[player] += totalPayout;

        // 4. Update player stats and registration
        // Note: 'wins' tracks the gross prize amount won, regardless of when it is claimed.
        wins[player] += totalPayout;

        // 5. Clean up VRF request state - RESTORED
        latestRandomWord = randomWords;
        // delete requestToPlayer[requestId];
        // delete requestRefunds[requestId];
        fulfilled = true;

        emit Win(player, prizeAmount, block.timestamp);
        emit VRFRandomnessFulfilled(requestId, prizes[prizeIndex]);
    }

    // =============================
    //          Claiming
    // =============================

    /**
     * @notice Allows a player to withdraw their accumulated unclaimed winnings and overpayment refunds.
     * @dev Uses a reentrancy guard. The player's balance is reset to zero before the transfer to prevent reentrancy attacks.
     */
    function claimWinnings() public nonReentrant {
        uint256 amount = unclaimedWinnings[msg.sender];
        require(amount > 0, "No unclaimed winnings to withdraw");

        // Set amount to zero BEFORE sending funds
        unclaimedWinnings[msg.sender] = 0;

        // Send funds to the player
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "Withdrawal failed");
    }

    /**
     * @notice Transfers the entire contract's ETH balance to the contract owner.
     * @dev Only the contract owner (`ConfirmedOwner`) can call this function. Uses a reentrancy guard.
     */
    function withdrawAll() public onlyOwner nonReentrant {
        uint256 amount = address(this).balance;
        require(amount > 0, "No balance");
        (bool success, ) = payable(owner()).call{value: amount}("");
        require(success, "Failed to withdraw");

        emit Withdrawal(owner(), amount, block.timestamp);
    }

    /**
     * @notice Pauses the secure game playing operation (`play`).
     * @dev Only the contract owner can call this function.
     */
    function pausePlaying() public onlyOwner {
        _pause();
    }

    /**
     * @notice Unpauses the secure game playing operation (`play`).
     * @dev Only the contract owner can call this function.
     */
    function unpausePlaying() public onlyOwner {
        _unpause();
    }
}

Other files

  • @chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    import {ConfirmedOwnerWithProposal} from "./ConfirmedOwnerWithProposal.sol";
    
    /// @title The ConfirmedOwner contract
    /// @notice A contract with helpers for basic contract ownership.
    contract ConfirmedOwner is ConfirmedOwnerWithProposal {
      constructor(
        address newOwner
      ) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
    }
    
  • @chainlink/contracts/src/v0.8/shared/access/ConfirmedOwnerWithProposal.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    import {IOwnable} from "../interfaces/IOwnable.sol";
    
    /// @title The ConfirmedOwner contract
    /// @notice A contract with helpers for basic contract ownership.
    contract ConfirmedOwnerWithProposal is IOwnable {
      address private s_owner;
      address private s_pendingOwner;
    
      event OwnershipTransferRequested(address indexed from, address indexed to);
      event OwnershipTransferred(address indexed from, address indexed to);
    
      constructor(address newOwner, address pendingOwner) {
        // solhint-disable-next-line gas-custom-errors
        require(newOwner != address(0), "Cannot set owner to zero");
    
        s_owner = newOwner;
        if (pendingOwner != address(0)) {
          _transferOwnership(pendingOwner);
        }
      }
    
      /// @notice Allows an owner to begin transferring ownership to a new address.
      function transferOwnership(
        address to
      ) public override onlyOwner {
        _transferOwnership(to);
      }
    
      /// @notice Allows an ownership transfer to be completed by the recipient.
      function acceptOwnership() external override {
        // solhint-disable-next-line gas-custom-errors
        require(msg.sender == s_pendingOwner, "Must be proposed owner");
    
        address oldOwner = s_owner;
        s_owner = msg.sender;
        s_pendingOwner = address(0);
    
        emit OwnershipTransferred(oldOwner, msg.sender);
      }
    
      /// @notice Get the current owner
      function owner() public view override returns (address) {
        return s_owner;
      }
    
      /// @notice validate, transfer ownership, and emit relevant events
      function _transferOwnership(
        address to
      ) private {
        // solhint-disable-next-line gas-custom-errors
        require(to != msg.sender, "Cannot transfer to self");
    
        s_pendingOwner = to;
    
        emit OwnershipTransferRequested(s_owner, to);
      }
    
      /// @notice validate access
      function _validateOwnership() internal view {
        // solhint-disable-next-line gas-custom-errors
        require(msg.sender == s_owner, "Only callable by owner");
      }
    
      /// @notice Reverts if called by anyone other than the contract owner.
      modifier onlyOwner() {
        _validateOwnership();
        _;
      }
    }
    
  • @chainlink/contracts/src/v0.8/shared/interfaces/IOwnable.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    interface IOwnable {
      function owner() external returns (address);
    
      function transferOwnership(
        address recipient
      ) external;
    
      function acceptOwnership() external;
    }
    
  • @chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    // solhint-disable-next-line interface-starts-with-i
    interface LinkTokenInterface {
      function allowance(address owner, address spender) external view returns (uint256 remaining);
    
      function approve(address spender, uint256 value) external returns (bool success);
    
      function balanceOf(
        address owner
      ) external view returns (uint256 balance);
    
      function decimals() external view returns (uint8 decimalPlaces);
    
      function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
    
      function increaseApproval(address spender, uint256 subtractedValue) external;
    
      function name() external view returns (string memory tokenName);
    
      function symbol() external view returns (string memory tokenSymbol);
    
      function totalSupply() external view returns (uint256 totalTokensIssued);
    
      function transfer(address to, uint256 value) external returns (bool success);
    
      function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);
    
      function transferFrom(address from, address to, uint256 value) external returns (bool success);
    }
    
  • @chainlink/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapperConsumerBase.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol";
    import {IVRFV2PlusWrapper} from "./interfaces/IVRFV2PlusWrapper.sol";
    
    /**
     *
     * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper
     * ********************************************************************************
     * @dev PURPOSE
     *
     * @dev Create VRF V2+ requests without the need for subscription management. Rather than creating
     * @dev and funding a VRF V2+ subscription, a user can use this wrapper to create one off requests,
     * @dev paying up front rather than at fulfillment.
     *
     * @dev Since the price is determined using the gas price of the request transaction rather than
     * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas
     * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract.
     * *****************************************************************************
     * @dev USAGE
     *
     * @dev Calling contracts must inherit from VRFV2PlusWrapperConsumerBase. The consumer must be funded
     * @dev with enough LINK or ether to make the request, otherwise requests will revert. To request randomness,
     * @dev call the 'requestRandomWords' function with the desired VRF parameters. This function handles
     * @dev paying for the request based on the current pricing.
     *
     * @dev Consumers must implement the fullfillRandomWords function, which will be called during
     * @dev fulfillment with the randomness result.
     */
    abstract contract VRFV2PlusWrapperConsumerBase {
      error OnlyVRFWrapperCanFulfill(address have, address want);
    
      LinkTokenInterface internal immutable i_linkToken;
      IVRFV2PlusWrapper public immutable i_vrfV2PlusWrapper;
    
      /**
       * @param _vrfV2PlusWrapper is the address of the VRFV2Wrapper contract
       */
      constructor(
        address _vrfV2PlusWrapper
      ) {
        IVRFV2PlusWrapper vrfV2PlusWrapper = IVRFV2PlusWrapper(_vrfV2PlusWrapper);
    
        i_linkToken = LinkTokenInterface(vrfV2PlusWrapper.link());
        i_vrfV2PlusWrapper = vrfV2PlusWrapper;
      }
    
      /**
       * @dev Requests randomness from the VRF V2+ wrapper.
       *
       * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's
       *        fulfillRandomWords function.
       * @param _requestConfirmations is the number of confirmations to wait before fulfilling the
       *        request. A higher number of confirmations increases security by reducing the likelihood
       *        that a chain re-org changes a published randomness outcome.
       * @param _numWords is the number of random words to request.
       *
       * @return requestId is the VRF V2+ request ID of the newly created randomness request.
       */
      // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
      function requestRandomness(
        uint32 _callbackGasLimit,
        uint16 _requestConfirmations,
        uint32 _numWords,
        bytes memory extraArgs
      ) internal returns (uint256 requestId, uint256 reqPrice) {
        reqPrice = i_vrfV2PlusWrapper.calculateRequestPrice(_callbackGasLimit, _numWords);
        i_linkToken.transferAndCall(
          address(i_vrfV2PlusWrapper), reqPrice, abi.encode(_callbackGasLimit, _requestConfirmations, _numWords, extraArgs)
        );
        return (i_vrfV2PlusWrapper.lastRequestId(), reqPrice);
      }
    
      // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
      function requestRandomnessPayInNative(
        uint32 _callbackGasLimit,
        uint16 _requestConfirmations,
        uint32 _numWords,
        bytes memory extraArgs
      ) internal returns (uint256 requestId, uint256 requestPrice) {
        requestPrice = i_vrfV2PlusWrapper.calculateRequestPriceNative(_callbackGasLimit, _numWords);
        return (
          i_vrfV2PlusWrapper.requestRandomWordsInNative{value: requestPrice}(
            _callbackGasLimit, _requestConfirmations, _numWords, extraArgs
          ),
          requestPrice
        );
      }
    
      /**
       * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must
       * @notice implement it.
       *
       * @param _requestId is the VRF V2 request ID.
       * @param _randomWords is the randomness result.
       */
      // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
      function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual;
    
      function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external {
        address vrfWrapperAddr = address(i_vrfV2PlusWrapper);
        if (msg.sender != vrfWrapperAddr) {
          revert OnlyVRFWrapperCanFulfill(msg.sender, vrfWrapperAddr);
        }
        fulfillRandomWords(_requestId, _randomWords);
      }
    
      /// @notice getBalance returns the native balance of the consumer contract
      function getBalance() public view returns (uint256) {
        return address(this).balance;
      }
    
      /// @notice getLinkToken returns the link token contract
      function getLinkToken() public view returns (LinkTokenInterface) {
        return i_linkToken;
      }
    }
    
  • @chainlink/contracts/src/v0.8/vrf/dev/interfaces/IVRFV2PlusWrapper.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    interface IVRFV2PlusWrapper {
      /**
       * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only
       * be relied option within the same transaction that the request was made.
       */
      function lastRequestId() external view returns (uint256);
    
      /**
       * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
       * @notice block.
       *
       * @dev This function relies on the transaction gas price which is not automatically set during
       * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
       *
       * @param _callbackGasLimit is the gas limit used to estimate the price.
       * @param _numWords is the number of words to request.
       */
      function calculateRequestPrice(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint256);
    
      /**
       * @notice Calculates the price of a VRF request in native with the given callbackGasLimit at the current
       * @notice block.
       *
       * @dev This function relies on the transaction gas price which is not automatically set during
       * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
       *
       * @param _callbackGasLimit is the gas limit used to estimate the price.
       * @param _numWords is the number of words to request.
       */
      function calculateRequestPriceNative(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint256);
    
      /**
       * @notice Estimates the price of a VRF request with a specific gas limit and gas price.
       *
       * @dev This is a convenience function that can be called in simulation to better understand
       * @dev pricing.
       *
       * @param _callbackGasLimit is the gas limit used to estimate the price.
       * @param _numWords is the number of words to request.
       * @param _requestGasPriceWei is the gas price in wei used for the estimation.
       */
      function estimateRequestPrice(
        uint32 _callbackGasLimit,
        uint32 _numWords,
        uint256 _requestGasPriceWei
      ) external view returns (uint256);
    
      /**
       * @notice Estimates the price of a VRF request in native with a specific gas limit and gas price.
       *
       * @dev This is a convenience function that can be called in simulation to better understand
       * @dev pricing.
       *
       * @param _callbackGasLimit is the gas limit used to estimate the price.
       * @param _numWords is the number of words to request.
       * @param _requestGasPriceWei is the gas price in wei used for the estimation.
       */
      function estimateRequestPriceNative(
        uint32 _callbackGasLimit,
        uint32 _numWords,
        uint256 _requestGasPriceWei
      ) external view returns (uint256);
    
      /**
       * @notice Requests randomness from the VRF V2 wrapper, paying in native token.
       *
       * @param _callbackGasLimit is the gas limit for the request.
       * @param _requestConfirmations number of request confirmations to wait before serving a request.
       * @param _numWords is the number of words to request.
       */
      function requestRandomWordsInNative(
        uint32 _callbackGasLimit,
        uint16 _requestConfirmations,
        uint32 _numWords,
        bytes calldata extraArgs
      ) external payable returns (uint256 requestId);
    
      function link() external view returns (address);
      function linkNativeFeed() external view returns (address);
    }
    
  • @chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.4;
    
    // End consumer library.
    library VRFV2PlusClient {
      // extraArgs will evolve to support new features
      bytes4 public constant EXTRA_ARGS_V1_TAG = bytes4(keccak256("VRF ExtraArgsV1"));
    
      struct ExtraArgsV1 {
        bool nativePayment;
      }
    
      struct RandomWordsRequest {
        bytes32 keyHash;
        uint256 subId;
        uint16 requestConfirmations;
        uint32 callbackGasLimit;
        uint32 numWords;
        bytes extraArgs;
      }
    
      function _argsToBytes(
        ExtraArgsV1 memory extraArgs
      ) internal pure returns (bytes memory bts) {
        return abi.encodeWithSelector(EXTRA_ARGS_V1_TAG, extraArgs);
      }
    }
    
  • @openzeppelin/contracts/utils/Context.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    
  • @openzeppelin/contracts/utils/Pausable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
    
    pragma solidity ^0.8.20;
    
    import {Context} from "../utils/Context.sol";
    
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    abstract contract Pausable is Context {
        bool private _paused;
    
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
    
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
    
        /**
         * @dev The operation failed because the contract is paused.
         */
        error EnforcedPause();
    
        /**
         * @dev The operation failed because the contract is not paused.
         */
        error ExpectedPause();
    
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        modifier whenNotPaused() {
            _requireNotPaused();
            _;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        modifier whenPaused() {
            _requirePaused();
            _;
        }
    
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view virtual returns (bool) {
            return _paused;
        }
    
        /**
         * @dev Throws if the contract is paused.
         */
        function _requireNotPaused() internal view virtual {
            if (paused()) {
                revert EnforcedPause();
            }
        }
    
        /**
         * @dev Throws if the contract is not paused.
         */
        function _requirePaused() internal view virtual {
            if (!paused()) {
                revert ExpectedPause();
            }
        }
    
        /**
         * @dev Triggers stopped state.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
    
        /**
         * @dev Returns to normal state.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
    }
    
  • @openzeppelin/contracts/utils/ReentrancyGuard.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
     * consider using {ReentrancyGuardTransient} instead.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     */
    abstract contract ReentrancyGuard {
        // Booleans are more expensive than uint256 or any type that takes up a full
        // word because each write operation emits an extra SLOAD to first read the
        // slot's contents, replace the bits taken up by the boolean, and then write
        // back. This is the compiler's defense against contract upgrades and
        // pointer aliasing, and it cannot be disabled.
    
        // The values being non-zero value makes deployment a bit more expensive,
        // but in exchange the refund on every call to nonReentrant will be lower in
        // amount. Since refunds are capped to a percentage of the total
        // transaction's gas, it is best to keep them low in cases like this one, to
        // increase the likelihood of the full refund coming into effect.
        uint256 private constant NOT_ENTERED = 1;
        uint256 private constant ENTERED = 2;
    
        uint256 private _status;
    
        /**
         * @dev Unauthorized reentrant call.
         */
        error ReentrancyGuardReentrantCall();
    
        constructor() {
            _status = NOT_ENTERED;
        }
    
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and making it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            _nonReentrantBefore();
            _;
            _nonReentrantAfter();
        }
    
        function _nonReentrantBefore() private {
            // On the first call to nonReentrant, _status will be NOT_ENTERED
            if (_status == ENTERED) {
                revert ReentrancyGuardReentrantCall();
            }
    
            // Any calls to nonReentrant after this point will fail
            _status = ENTERED;
        }
    
        function _nonReentrantAfter() private {
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            _status = NOT_ENTERED;
        }
    
        /**
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
         * `nonReentrant` function in the call stack.
         */
        function _reentrancyGuardEntered() internal view returns (bool) {
            return _status == ENTERED;
        }
    }
    
ABI · 58 entries
json
[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "wrapper",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "payable"
  },
  {
    "name": "EnforcedPause",
    "type": "error",
    "inputs": []
  },
  {
    "name": "ExpectedPause",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InsufficientContractBalance",
    "type": "error",
    "inputs": [
      {
        "name": "required",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "current",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "InsufficientPayment",
    "type": "error",
    "inputs": [
      {
        "name": "providedAmount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "requiredFee",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "OnlyVRFWrapperCanFulfill",
    "type": "error",
    "inputs": [
      {
        "name": "have",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "want",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "PrizeNotFound",
    "type": "error",
    "inputs": [
      {
        "name": "attempts",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "ReentrancyGuardReentrantCall",
    "type": "error",
    "inputs": []
  },
  {
    "name": "RequestAlreadyPending",
    "type": "error",
    "inputs": []
  },
  {
    "name": "VRFRequestMismatch",
    "type": "error",
    "inputs": [
      {
        "name": "providedRequestId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ]
  },
  {
    "name": "OwnershipTransferRequested",
    "type": "event",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "OwnershipTransferred",
    "type": "event",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Paused",
    "type": "event",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "PrizesUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "newPrizes",
        "type": "uint256[]",
        "indexed": false,
        "internalType": "uint256[]"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Unpaused",
    "type": "event",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "VRFRandomnessFulfilled",
    "type": "event",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "prizeAmount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "VRFRandomnessRequested",
    "type": "event",
    "inputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "requester",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "paidAmount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Win",
    "type": "event",
    "inputs": [
      {
        "name": "player",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "prizeAmount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Withdrawal",
    "type": "event",
    "inputs": [
      {
        "name": "recipient",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "MAX_PRIZE_RESERVE_ETH",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "VRF_CALLBACK_GAS_LIMIT",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "VRF_NUM_WORDS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "VRF_REQUEST_CONFIRMATIONS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "acceptOwnership",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "allPlayers",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "claimWinnings",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "fee",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "fulfilled",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getAllPrizes",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getBalance",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getLatestRandomWord",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getLinkToken",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract LinkTokenInterface"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getPlayersBatch",
    "type": "function",
    "inputs": [
      {
        "name": "startIndex",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "batchSize",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple[]",
        "components": [
          {
            "name": "player",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "wins",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "played",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct Booloot.PlayerData[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getRequestStatus",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "requestId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "isPending",
        "type": "bool",
        "internalType": "bool"
      },
      {
        "name": "isFulfilled",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getVRFRequestPrice",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "i_vrfV2PlusWrapper",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract IVRFV2PlusWrapper"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "isPlayerRegistered",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "latestRandomWord",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "latestRequestId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "owner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pausePlaying",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "paused",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "play",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "payable"
  },
  {
    "name": "players",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "prizes",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "rawFulfillRandomWords",
    "type": "function",
    "inputs": [
      {
        "name": "_requestId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_randomWords",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "requestRefunds",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "requestToPlayer",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "rng",
    "type": "function",
    "inputs": [
      {
        "name": "len",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "nonce",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "testTest",
    "type": "function",
    "inputs": [
      {
        "name": "randomWords",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "transferOwnership",
    "type": "function",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "unclaimedWinnings",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "uniquePlayerCount",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "unpausePlaying",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "updateFee",
    "type": "function",
    "inputs": [
      {
        "name": "_fee",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "updatePrizes",
    "type": "function",
    "inputs": [
      {
        "name": "_prizes",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "wins",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "withdrawAll",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  }
]