# Quickstart (/docs/builders/vrf/quickstart)

import { Steps, Step } from 'fumadocs-ui/components/steps';
import { Tabs, Tab } from 'fumadocs-ui/components/tabs';

# VRF Quickstart

Build a dice game with verifiable randomness and realtime updates in under 15 minutes. This tutorial demonstrates VRF integration with instant result notifications.

<Callout type="info" title="Testnet Only">
  This quickstart is designed for testnet development and testing. For mainnet production deployment, additional security measures including cryptographic proof verification and spam prevention are required. See the [Smart Contracts](/docs/builders/vrf/smart-contracts) page for details.
</Callout>

## What We'll Build

A dice game that:

* Requests verifiable random numbers
* Tracks player streaks for rolling sixes
* Updates UI in realtime via WebSocket
* Handles VRF callbacks securely

## Prerequisites

* Node.js 16+ installed
* Basic Solidity knowledge
* MetaMask or similar wallet
* RISE testnet tokens

## Smart Contract Setup

<Steps>
  <Step>
    ### Create the Project

    ```bash
    mkdir vrf-dice-game
    cd vrf-dice-game
    forge init
    ```
  </Step>

  <Step>
    ### Write the Dice Game Contract

    Create `src/DiceGame.sol`:

    ```solidity title="src/DiceGame.sol"
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;

    interface IVRFCoordinator {
        function requestRandomNumbers(uint32 numNumbers, uint256 seed) external returns (uint256);
    }

    interface IVRFConsumer {
        function rawFulfillRandomNumbers(
            uint256 requestId,
            uint256[] memory randomNumbers
        ) external;
    }

    contract DiceGame is IVRFConsumer {
        IVRFCoordinator public coordinator;

        mapping(address => bool) public hasPendingRoll;
        mapping(address => uint256) public currentStreak;
        mapping(address => uint256) public topStreak;
        mapping(uint256 => address) public requestOwners;

        uint256 public requestCount = 0;
        uint8 private constant DICE_SIDES = 6;

        event DiceRollRequested(address indexed player, uint256 indexed requestId);
        event DiceRollCompleted(
            address indexed player,
            uint256 indexed requestId,
            uint256 result,
            uint256 currentStreak,
            uint256 topStreak
        );
        event NewTopScore(address indexed player, uint256 newTopStreak);

        constructor(address _coordinator) {
            coordinator = IVRFCoordinator(_coordinator);
        }

        function rollDice() external returns (uint256 requestId) {
            require(!hasPendingRoll[msg.sender], "Already has a pending roll");

            hasPendingRoll[msg.sender] = true;
            uint256 seed = requestCount++;
            requestId = coordinator.requestRandomNumbers(1, seed);
            requestOwners[requestId] = msg.sender;

            emit DiceRollRequested(msg.sender, requestId);
            return requestId;
        }

        function rawFulfillRandomNumbers(
            uint256 requestId,
            uint256[] memory randomNumbers
        ) external {
            require(msg.sender == address(coordinator), "Only coordinator can fulfill");
            require(randomNumbers.length > 0, "No random numbers provided");

            address player = requestOwners[requestId];
            require(player != address(0), "Unknown request ID");
            require(hasPendingRoll[player], "No pending roll");

            uint256 diceRoll = (randomNumbers[0] % DICE_SIDES) + 1;

            if (diceRoll == 6) {
                currentStreak[player] += 1;
                if (currentStreak[player] > topStreak[player]) {
                    topStreak[player] = currentStreak[player];
                    emit NewTopScore(player, topStreak[player]);
                }
            } else {
                currentStreak[player] = 0;
            }

            hasPendingRoll[player] = false;

            emit DiceRollCompleted(
                player,
                requestId,
                diceRoll,
                currentStreak[player],
                topStreak[player]
            );

            delete requestOwners[requestId];
        }
    }
    ```
  </Step>

  <Step>
    ### Deploy to RISE Testnet

    Configure `foundry.toml`:

    ```toml title="foundry.toml"
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]
    solc_version = "0.8.19"

    [rpc_endpoints]
    rise_testnet = "https://testnet.riselabs.xyz"

    [etherscan]
    rise_testnet = { key = "", url = "https://explorer.testnet.riselabs.xyz/api" }
    ```

    Deploy:

    ```bash
    forge create --rpc-url rise_testnet \
      --private-key $PRIVATE_KEY \
      --constructor-args 0x9d57aB4517ba97349551C876a01a7580B1338909 \
      src/DiceGame.sol:DiceGame
    ```

    Verify on Blockscout:

    ```bash
    forge verify-contract <DEPLOYED_ADDRESS> \
      src/DiceGame.sol:DiceGame \
      --chain 11155931 \
      --verifier blockscout \
      --verifier-url https://explorer.testnet.riselabs.xyz/api \
      --constructor-args $(cast abi-encode "constructor(address)" 0x9d57aB4517ba97349551C876a01a7580B1338909)
    ```
  </Step>
</Steps>

## Frontend Integration

<Steps>
  <Step>
    ### Setup Frontend

    <Tabs items={['npm', 'pnpm', 'yarn', 'bun']}>
      <Tab value="npm">
        ```bash
        npm install viem
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm add viem
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn add viem
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bun add viem
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ### Monitor VRF Events

    Create `monitor.ts` to track dice roll events:

    ```typescript title="monitor.ts"
    import { createPublicClient, webSocket } from 'viem';
    import { riseTestnet } from 'viem/chains';

    const wsUrl = 'wss://testnet.riselabs.xyz/ws';
    const diceGameAddress = '0x...'; // Your deployed contract

    const vrfAbi = [
      {
        type: 'event',
        name: 'DiceRollCompleted',
        inputs: [
          { name: 'player', type: 'address', indexed: true },
          { name: 'requestId', type: 'uint256', indexed: true },
          { name: 'result', type: 'uint256' },
          { name: 'currentStreak', type: 'uint256' },
          { name: 'topStreak', type: 'uint256' }
        ]
      }
    ] as const;

    const client = createPublicClient({
      chain: riseTestnet,
      transport: webSocket(wsUrl)
    });

    const unwatch = client.watchContractEvent({
      address: diceGameAddress,
      abi: vrfAbi,
      eventName: 'DiceRollCompleted',
      onLogs: (logs) => {
        logs.forEach((log) => {
          console.log(`Player rolled: ${log.args.result}`);
          console.log(`Streak: ${log.args.currentStreak}`);
        });
      }
    });
    ```
  </Step>
</Steps>

## How It Works

1. **User initiates**: Click roll button in the DApp
2. **Request randomness**: DApp calls `rollDice()` on the contract
3. **VRF processing**: Contract requests random numbers from VRF coordinator
4. **Instant callback**: VRF calls `rawFulfillRandomNumbers()` with result
5. **Event emission**: Contract emits `DiceRollCompleted` event
6. **Realtime update**: WebSocket delivers event to DApp in \~4ms
7. **UI update**: DApp shows the dice result immediately

## Key Takeaways

* **Instant Results**: VRF responds in milliseconds via shreds
* **Realtime Updates**: WebSocket delivers events instantly
* **Secure Randomness**: Cryptographically verifiable numbers
* **Simple Integration**: Standard Solidity interfaces

## Next Steps

* Add betting mechanics with token stakes
* Create multiplayer dice battles
* Implement leaderboards
* Add different game modes
