Learn how to build apps on Abstract like Gacha, which use Proof of Play’s verifiable random number generator (vRNG) service to provide fair, on-chain randomness for outcomes.

1. Foundry project setup

1

Create a Foundry project

Create a Foundry project (or configure an existing one) following our Foundry guide.

Get Started with Foundry

Learn how to install and configure foundry-zksync for Abstract.
2

Install Absmate

Install Absmate; a collection of helpful utilities for building contracts on Abstract.
forge install abstract-foundation/absmate
3

Configure remappings

Add the following to your remappings.txt file to use Absmate’s contracts.
remappings.txt
absmate/=lib/absmate/

2. Create a game contract

Create a contract that requests and receives random numbers by extending VRNGConsumer.
src/CoinFlipGame.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {VRNGConsumer} from "absmate/src/utils/vrng/VRNGConsumer.sol";

contract CoinFlipGame is VRNGConsumer {
    mapping(uint256 => address) public games;

    constructor(address vrngSystem) {
        _setVRNG(vrngSystem); // Initialize vRNG system
    }

    // 1 - Request a random number from vRNG service
    function playGame() external returns (uint256 requestId) {
        // Request randomness from vRNG service
        requestId = _requestRandomNumber();

        games[requestId] = msg.sender;
        return requestId;
    }

    // 2 - Receive a random number from vRNG service in this callback
    function _onRandomNumberFulfilled(
        uint256 requestId,
        uint256 randomNumber
    ) internal override {
        // Use random number to determine some game outcome logic.
        bool playerWon = (randomNumber % 2) == 0;
    }
}

3. Test with mock contracts

Test your game contract behaviour with Absmate’s mock contracts.
1

Create a test contract

Use the MockVRNGSystem contract to mock the VRNG system in your tests.Pass its contract address to your game contract, by providing it to the _setVRNG function (here we do it via a constructor argument to the CoinFlipGame contract).
test/CoinFlipGameTest.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Test} from "forge-std/Test.sol";
import {MockVRNGSystem} from "absmate/test/mocks/MockVRNGSystem.sol";
import {CoinFlipGame} from "../src/CoinFlipGame.sol";

contract CoinFlipGameTest is Test {
    MockVRNGSystem mockVRNG;
    CoinFlipGame game;

    function setUp() public {
        mockVRNG = new MockVRNGSystem(); // deploy mockVRNG
        game = new CoinFlipGame(address(mockVRNG)); // call _setVRNG with mockVRNG address
    }
}
2

Test your contract with request/fulfill pattern mocks

Mock the two-phase request/fulfill pattern:
  1. Request a random number from the vRNG service.
  2. Mock the callback function execution using randomNumberCallback.
test/CoinFlipGameTest.t.sol
function testCoinFlip() public {
    address player = address(0x123);
    
    // Request randomness from vRNG service
    vm.prank(player);
    uint256 requestId = game.playGame();

    // Simulate vRNG callback with controlled value
    vm.prank(address(mockVRNG));
    uint256 randomNumber = 2; // control the random number delivered
    game.randomNumberCallback(requestId, randomNumber);
    
    // Verify some behaviour based on the random number delivered
    assertEq(game.games(requestId), player);
}
3

Run your tests

Verify your contract behaves as expected. See Foundry testing for more details.
forge test --zksync

4. Deploy your contract

1

Create a deploy script

Create a deploy script to deploy your game contract with the Proof of Play vRNG contract address based on the chain ID.
script/DeployCoinFlipGame.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Script} from "forge-std/Script.sol";
import {CoinFlipGame} from "../src/CoinFlipGame.sol";

contract DeployCoinFlipGame is Script {
    function run() external {
        uint256 chainId = block.chainid;
        address vrngSystemAddress = _getProofOfPlayVRNGAddress(chainId);
        require(vrngSystemAddress != address(0), "Unsupported chain");

        vm.startBroadcast();
        CoinFlipGame coinFlipGame = new CoinFlipGame(vrngSystemAddress);
        vm.stopBroadcast();
    }

    function _getProofOfPlayVRNGAddress(
        uint256 chainId
    ) internal pure returns (address) {
        return
            chainId == 2741 // Abstract Mainnet
                ? 0xBDC8B6eb1840215A22fC1134046f595b7D42C2DE
                : chainId == 11124 // Abstract Testnet
                    ? 0xC04ae87CDd258994614f7fFB8506e69B7Fd8CF1D
                    : address(0);
    }
}
2

Run the deploy script

Deploy your contract to Abstract using the deploy script.
forge script script/DeployCoinFlipGame.s.sol:DeployCoinFlipGame \
  --account myKeystore \
  --rpc-url https://api.testnet.abs.xyz \
  --chain 11124 \
  --zksync \
  --broadcast \
  --verify \
  --verifier etherscan \
  --verifier-url https://api-sepolia.abscan.org/api \
  --etherscan-api-key TACK2D1RGYX9U7MC31SZWWQ7FCWRYQ96AD
3

Contact the Proof of Play team

The Proof of Play vRNG contracts currently require your contract address to be whitelisted to enable the randomNumberCallback function to be called.Contact the Proof of Play team to whitelist your smart contract address.

Contact the Proof of Play team

Contact the Proof of Play team to whitelist your smart contract address.