# Hands On Game Using VRF

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FH2antEDCzoQZcDivz2y0%2FScreenshot%202024-04-30%20at%2009.02.36.png?alt=media&#x26;token=f07ead3d-1f42-4ee4-8c53-02e72eedfefa" alt=""><figcaption></figcaption></figure>

#### Deploying Our Game in Remix

For this demo, we will be using Remix IDE. Make sure you've [completed Section 2 ](https://cll-devrel.gitbook.io/bootcamp-2024/2.-smart-contract-and-solidity-fundamentals)and [Section 3 ](https://cll-devrel.gitbook.io/bootcamp-2024/6.-nfts-and-chainlink-automation)before attempting this exercise.

{% embed url="<https://remix.ethereum.org/>" %}

First create a file called **Runners.sol** and paste the following code (make sure to adjust your Remix compiler to refer to the compiler version in the pragma statement below):

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// Deploy this contract on Fuji

import "@openzeppelin/contracts@4.6.0/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.6.0/utils/Counters.sol";
import "@openzeppelin/contracts@4.6.0/utils/Base64.sol";

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";

contract Runners is ERC721, ERC721URIStorage, VRFConsumerBaseV2  {
    using Counters for Counters.Counter;
    using Strings for uint256;

    // VRF
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(uint256 requestId, uint256[] randomWords);

    struct RequestStatus {
        bool fulfilled; // whether the request has been successfully fulfilled
        bool exists; // whether a requestId exists
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */          

    // Fuji coordinator
    // https://docs.chain.link/docs/vrf/v2/subscription/supported-networks/
    VRFCoordinatorV2Interface COORDINATOR;
    address vrfCoordinator = 0x2eD832Ba664535e5886b75D64C46EB9a228C2610;
    bytes32 keyHash = 0x354d2f95da55398f44b7cff77da56283d9c6c829a4bdf1bbcaf2ad6a4d081f61;
    uint32 callbackGasLimit = 2500000;
    uint16 requestConfirmations = 3;
    uint32 numWords =  1;

    // past requests Ids.
    uint256[] public requestIds;
    uint256 public lastRequestId;
    uint256[] public lastRandomWords;

    // Your subscription ID.
    uint64 public s_subscriptionId;

    //Runners NFT
    Counters.Counter public tokenIdCounter;
    string[] characters_image = [
        "https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png",
        "https://ipfs.io/ipfs/QmZGQA92ri1jfzSu61JRaNQXYg1bLuM7p8YT83DzFA2KLH?filename=Chainlink_Knight.png",
        "https://ipfs.io/ipfs/QmW1toapYs7M29rzLXTENn3pbvwe8ioikX1PwzACzjfdHP?filename=Chainlink_Orc.png",
        "https://ipfs.io/ipfs/QmPMwQtFpEdKrUjpQJfoTeZS1aVSeuJT6Mof7uV29AcUpF?filename=Chainlink_Witch.png"
    ];
    string[] characters_name = [
        "Elf",
        "Knight",
        "Orc",
        "Witch"
    ];

    struct Runner {
        string name;
        string image;
        uint256 distance;
        uint256 round;
    }
    Runner[] public runners;
    mapping(uint256 => uint256) public request_runner; /* requestId --> tokenId*/


    constructor(uint64 subscriptionId) ERC721("Runners", "RUN")
        VRFConsumerBaseV2(vrfCoordinator)        
    {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        s_subscriptionId = subscriptionId;
        safeMint(msg.sender,0);
    }

    function safeMint(address to, uint256 charId) public {
        uint8 aux = uint8 (charId);
        require( (aux >= 0) && (aux <= 3), "invalid charId");
        string memory yourCharacterImage = characters_image[charId];

        runners.push(Runner(characters_name[charId], yourCharacterImage, 0, 0));

        uint256 tokenId = tokenIdCounter.current();
        string memory uri = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "', runners[tokenId].name, '",'
                        '"description": "Chainlink runner",',
                        '"image": "', runners[tokenId].image, '",'
                        '"attributes": [',
                            '{"trait_type": "distance",',
                            '"value": ', runners[tokenId].distance.toString(),'}',
                            ',{"trait_type": "round",',
                            '"value": ', runners[tokenId].round.toString(),'}',
                        ']}'
                    )
                )
            )
        );
        // Create token URI
        string memory finalTokenURI = string(
            abi.encodePacked("data:application/json;base64,", uri)
        );
        tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, finalTokenURI);
    }


    function run(uint256 tokenId) external returns (uint256 requestId) {

        require (tokenId < tokenIdCounter.current(), "tokenId not exists");

        // Will revert if subscription is not set and funded.
        requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            randomWords: new uint256[](0),
            exists: true,
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        request_runner[requestId] = tokenId;
        return requestId;      
    }

    function fulfillRandomWords(
        uint256 _requestId, /* requestId */
        uint256[] memory _randomWords
    ) internal override {
        require (tokenIdCounter.current() >= 0, "You must mint a NFT");
        require(s_requests[_requestId].exists, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        lastRandomWords = _randomWords;

        uint aux = (lastRandomWords[0] % 10 + 1) * 10;
        uint256 tokenId = request_runner[_requestId];
        runners[tokenId].distance += aux;
        runners[tokenId].round ++;

        string memory uri = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "', runners[tokenId].name, '",'
                        '"description": "Chainlink runner",',
                        '"image": "', runners[tokenId].image, '",'
                        '"attributes": [',
                            '{"trait_type": "distance",',
                            '"value": ', runners[tokenId].distance.toString(),'}',
                            ',{"trait_type": "round",',
                            '"value": ', runners[tokenId].round.toString(),'}',
                        ']}'
                    )
                )
            )
        );
        // Create token URI
        string memory finalTokenURI = string(
            abi.encodePacked("data:application/json;base64,", uri)
        );
        _setTokenURI(tokenId, finalTokenURI);
    }

    function getRequestStatus(
        uint256 _requestId
    ) external view returns (bool fulfilled, uint256[] memory randomWords) {

    }

    // The following functions are overrides required by Solidity.

    function tokenURI(uint256 tokenId)
        public view override(ERC721, ERC721URIStorage) returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage)
    {
        super._burn(tokenId);
    }
}
```

In our constructor, you can see we are passing in a **subscriptionId** which means we first need to set up our VRF subscription here:

{% embed url="<https://vrf.chain.link/>" %}

A VRF Subscription is a way to ensure that the decentralized Chainlink VRF service that sends your smart contract verifiably random numbers is "funded" with LINK.

For this exercise, let's use the Avalanche Fuji testnet. &#x20;

First ensure you are connected to **Avalanche Fuji** network in your wallet. Then select **Create Subscription.** This subscription will be used to manage your VRF service for your smart contract.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FxgYdtoDD6Y6xGkog6Pwh%2FScreenshot%202024-04-30%20at%2009.36.31.png?alt=media&#x26;token=00bbf22f-7daa-43fb-879c-e963b2959ab6" alt=""><figcaption><p>Select 'Create Subscription'</p></figcaption></figure>

On the **Create Subscription** page, enter a name for your subscription (optional) and click **create subscription** again.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FgpQUT8y5C3FoWP4hZYje%2FScreenshot%202024-04-30%20at%2019.43.49.png?alt=media&#x26;token=7d0ae1df-cc83-4ec6-a8ad-8637bd77a1d6" alt=""><figcaption><p>Select 'Create subscription'</p></figcaption></figure>

Then you will **sign** and **confirm** the following metamask transactions. After you will see the following pop-up on your subscription page. You must wait for the transaction to be confirmed for a few seconds, after which you will see **add funds** which you must select.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FctamLlNRIQzghoFiMgRI%2FScreenshot%202024-04-30%20at%2019.46.31.png?alt=media&#x26;token=34b78243-550e-4131-b357-69f5da629962" alt=""><figcaption><p>Select 'Add Funds'</p></figcaption></figure>

After you click add funds you will be prompted to enter an amount of **LINK** tokens (if you need more testnet LINK tokens and AVAX tokens, head to <https://faucets.chain.link/fuji>).&#x20;

For this demo we will be adding **10 LINK**, then select **confirm** and confirm the following transaction.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FIknJH0Wk4yr43SNDZGFb%2FScreenshot%202024-04-30%20at%2019.56.58.png?alt=media&#x26;token=ab8910a0-a477-4546-a36e-fd426d0287b9" alt=""><figcaption><p>Select 'Confirm'</p></figcaption></figure>

Now we will be directed to the subscription page. From this page we need to copy our **ID** which is our **subscriptionId**.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FEX0xU5qBtp3c0J00rnQU%2FScreenshot%202024-04-30%20at%2020.17.57.png?alt=media&#x26;token=ba23f418-e6a8-4826-82fa-9cb05f7228a2" alt=""><figcaption></figcaption></figure>

Now we want to head back to our remix to the **Deploy and run transactions** tab, select environment **Injected Provider - Metamask** and make sure we are still on Avalanche Fuji Network (43113).&#x20;

Then with our copied subscriptionId we will deploy the **Runners.sol** contract, then select the orange **Deploy** button

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FlPLu4fv4Ugfkl6uctEDK%2FScreenshot%202024-04-30%20at%2020.20.21.png?alt=media&#x26;token=7596273f-545d-4de8-8569-db3bf089643e" alt=""><figcaption><p>Paste your subcriptionId and select 'Deploy'</p></figcaption></figure>

In our constructor we minted an NFT, so now we will go to [**Opensea**](https://testnets.opensea.io/) and have a look at this NFT. To do this, you must copy your contract address. Note. Opensea does not support all testets, but it does support Avalanche Fuji. Here is a [list of  testnets that Opensea supports](https://docs.opensea.io/reference/supported-chains#testnets).&#x20;

{% embed url="<https://testnets.opensea.io/>" %}

Next, paste your contract address in the Openseas search bar and select your collection. You should now see an NFT minted which is the **Elf**.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2Ff0UiA0ElxEtjzmxl3xhZ%2FScreenshot%202024-04-30%20at%2020.24.49.png?alt=media&#x26;token=8302702f-9786-4fe8-a162-dba7443f7839" alt=""><figcaption></figcaption></figure>

We now want to take our copied contract address, and head back to our VRF Subscription page. On this page we need to select **Add Consumer**.&#x20;

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2F7SDOpFq0eE5J7tajiwri%2FScreenshot%202024-04-30%20at%2020.27.46.png?alt=media&#x26;token=3d3f970e-f1c6-417a-bfaf-650dec7bac5d" alt=""><figcaption><p>Select 'Add consumer'</p></figcaption></figure>

This will prompt you to enter you consumer address, where you will paste your contract address and click **add consumer.**

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FdBqyUzTk4BVholc7OFlh%2FScreenshot%202024-04-30%20at%2020.29.38.png?alt=media&#x26;token=18a7b597-3564-4691-b221-ad8143a18a02" alt=""><figcaption><p>Select 'Add consumer'</p></figcaption></figure>

Once this transaction is confirmed, we will head back to our contract in remix, open it, and call the orange **run** function passing in '0' for the parameter **tokenId**. Then click **transact** to execute this function.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FyucfRWkZkIaTPS6Ys4gu%2FScreenshot%202024-04-30%20at%2020.32.10.png?alt=media&#x26;token=4a1bf969-0ff6-49b5-bb00-c0d39fadc8bd" alt=""><figcaption><p>Pass 0 as a parameter for 'tokenId' in the 'run' function then select transact</p></figcaption></figure>

Once the transaction has been executed, scroll down until you see the blue **lastRequestId** function and select this. This will return a long number which is the **requestId** for your call to VRF so copy this.

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FrE9qVYtTOCfYb7DM36jR%2FScreenshot%202024-04-30%20at%2020.33.36.png?alt=media&#x26;token=97b0bd25-6f32-460d-9231-f9109318a1cc" alt=""><figcaption></figcaption></figure>

After some time has passed we will then paste this requestId into the blue **s\_requests** function and we should expect to see the following as **true**

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FoFWM3JnfomeVtkHsAWlo%2FScreenshot%202024-04-30%20at%2020.54.09.png?alt=media&#x26;token=56388bdd-77cc-4a7a-88c3-436f295000c6" alt=""><figcaption><p>Both 'fulfilled' and 'exists' are 'true'</p></figcaption></figure>

Now we will call the blue **runners** function, passing '0' as a parameter

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2FNEqeyZ9nN3mPUKKs803A%2FScreenshot%202024-04-30%20at%2020.56.57.png?alt=media&#x26;token=1d25c430-f7ea-4059-9a8a-8e0f1b327fae" alt=""><figcaption></figcaption></figure>

You will notice that your value for **distance** may be different, mine was '50'. This is because Chainlink VRF was used to generate this number randomly - we cannot predict it or know it in advance! Random!

Lastly we will head back to Opensea and view the previously minted NFT. If we scroll down and open the **Levels** tab you will see that the NFT metadata has been updated with this change!

<figure><img src="https://62720068-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNQrWPKDHvXg0HQjwzHbY%2Fuploads%2F9Gn93ezV26HNDQsYP1GL%2FScreenshot%202024-04-30%20at%2021.00.40.png?alt=media&#x26;token=514d79f8-b855-4ae1-b24d-1b51c2567394" alt=""><figcaption></figcaption></figure>
