Skip to content

witnet/my-witnet-roulette

Repository files navigation

WitnetRoulette

This repository is centered on contracts/WitnetRoulette.sol: a round-based roulette contract that consumes verifiable randomness from the Wit/Oracle Framework through Witnet's UsingWitRandomness integration.

The frontend remains in the repo, but the main purpose of this workspace is to demonstrate how an onchain game can actively depend on a Witnet randomness appliance for fair, auditable results.

Main Idea

WitnetRoulette does not generate randomness locally and does not trust block timestamps, block hashes, or operator-supplied values. Instead, it reads certified randomness from a deployed Witnet randomness appliance.

At a high level:

  1. Players place wagers for a future round.
  2. A Witnet randomness appliance is expected to have a successful randomization request on or after that round block.
  3. Once the appliance reports the round as randomized, the contract derives the roulette result from the Witnet-certified randomness.
  4. Players or the house claim winnings against that verifiable result.

This is the active-consumer model: your application logic actively depends on a live Witnet randomness appliance and reads randomness from it when business logic needs to settle outcomes.

Contract Focus

The core contract is contracts/WitnetRoulette.sol.

It uses:

import "@witnet/solidity/contracts/UsingWitRandomness.sol";

and inherits from UsingWitRandomness, which gives it access to the configured randomness appliance through the internal __witRandomness reference and helper methods such as _fetchRandomnessAfter(...).

How Verifiable Randomness Is Consumed

1. Configure a Witnet randomness appliance

The constructor receives an IWitRandomness appliance address:

constructor(
		  IWitRandomness randomizer,
		  uint16 roundIntervalBlocks,
		  uint16 ownerClaimDelayRounds
	 )

That appliance is validated by UsingWitRandomness and becomes the contract's source of truth for randomness.

2. Schedule game rounds by block number

Roulette rounds are aligned to multiples of ROUND_INTERVAL_BLOCKS.

  • nextRoundNumber() computes the next eligible round block.
  • wagerOnNumber(...) always stores bets for that future round.

This means bets are committed before the round they belong to is reached.

3. Wait until Witnet marks the round as randomized

The contract checks:

__witRandomness.isRandomized(roundNumber)

This gate is used before returning or settling results. If the appliance has not finalized a randomness value for that round yet, the contract will not settle it.

4. Derive the roulette outcome from certified randomness

The actual roulette result is obtained through:

_fetchRoundResult(roundNumber)

which internally uses:

_castADice(
	 MAX_NUMBER + 1,
	 roundNumber,
	 _fetchRandomnessAfter(roundNumber)
)

This gives a result in [0, 36].

  • 1..36 are player-winning numbers.
  • 0 is the house-winning number.

5. Settle claims against the certified result

  • fetchRoundResult(roundNumber) exposes the round result as a view function.
  • winningsOf(player, roundNumber, number) computes claimable winnings for a specific outcome.
  • claimWinnings(roundNumber, number) transfers funds if the supplied number matches the Witnet-derived result.

Because the outcome is derived from the Witnet appliance, anyone reading the same randomized round gets the same verifiable result.

What “Active Consumer” Means Here

In this repo, WitnetRoulette is an active consumer in the sense that it is wired to a Witnet randomness appliance and actively reads certified randomness as part of its settlement logic.

Important nuance:

  • The contract consumes randomness directly.
  • The contract does not itself post randomize() requests to the appliance.
  • An operator, bot, backend, or separate workflow must ensure the configured Witnet appliance is being fed with randomness requests for the rounds your app expects to settle.

So the operational model is:

  1. Keep the Witnet randomness appliance live.
  2. Ensure randomization requests are posted for the relevant blocks.
  3. Let WitnetRoulette read and consume the certified result when claims happen.

Running a Witnet Randomizer Bot (Backend)

If you want rounds to be continuously randomized, run a backend bot that sends randomize() requests to the same Witnet randomness appliance used by your deployed roulette contract.

The bot needs:

  • an ETH/RPC signing gateway (from ethrpc-gateway) with funds and a signer key
  • the target Witnet randomizer address (the same address passed as the randomizer constructor argument)
  • a schedule and network configuration

Core environment variables (see .env_witnet for the full template):

  • WITNET_SOLIDITY_RANDOMIZER_TARGET (required)
  • WITNET_SOLIDITY_RANDOMIZER_NETWORK (for example, gnosis:testnet)
  • WITNET_SOLIDITY_RANDOMIZER_GATEWAY_HOST (for example, http://127.0.0.1)
  • WITNET_SOLIDITY_RANDOMIZER_GATEWAY_PORT (for example, 8545)
  • WITNET_SOLIDITY_RANDOMIZER_SCHEDULE (cron expression)
  • WITNET_SOLIDITY_RANDOMIZER_SCHEDULE_TIMEZONE (for example, UTC)

Option A: Run the randomizer binary from @witnet/solidity

  1. Start a signing gateway (example: Gnosis Chiado):
$env:ETHRPC_PROVIDER_URL="https://rpc.chiadochain.net"
$env:ETHRPC_PRIVATE_KEYS='["0xyourPrivateKey"]'
npx.cmd ethrpc-ethers $env:ETHRPC_PROVIDER_URL 8545
  1. In a second terminal, run the randomizer bot:
$env:WITNET_SOLIDITY_RANDOMIZER_NETWORK="gnosis:testnet"
$env:WITNET_SOLIDITY_RANDOMIZER_TARGET="0xYourWitnetRandomnessAppliance"
$env:WITNET_SOLIDITY_RANDOMIZER_GATEWAY_HOST="http://127.0.0.1"
$env:WITNET_SOLIDITY_RANDOMIZER_GATEWAY_PORT="8545"
$env:WITNET_SOLIDITY_RANDOMIZER_SCHEDULE="*/5 * * * *"
$env:WITNET_SOLIDITY_RANDOMIZER_SCHEDULE_TIMEZONE="UTC"
npx.cmd --yes --package @witnet/solidity randomizer

Option B: Run a witnet/evm-randomizer Docker container

  1. Create an env file for the container, for example .env.randomizer:
WITNET_SOLIDITY_RANDOMIZER_NETWORK=gnosis:testnet
WITNET_SOLIDITY_RANDOMIZER_TARGET=0xYourWitnetRandomnessAppliance
WITNET_SOLIDITY_RANDOMIZER_GATEWAY_HOST=http://host.docker.internal
WITNET_SOLIDITY_RANDOMIZER_GATEWAY_PORT=8545
WITNET_SOLIDITY_RANDOMIZER_SCHEDULE=*/5 * * * *
WITNET_SOLIDITY_RANDOMIZER_SCHEDULE_TIMEZONE=UTC
  1. Run the container:
docker run --name witnet-randomizer --restart unless-stopped --env-file .env.randomizer witnet/evm-randomizer:3.1.0

Notes:

  • If your gateway runs on the host machine, use host.docker.internal as gateway host from inside the container.
  • Make sure the signing account behind the gateway has enough native gas token to keep posting randomize() transactions.
  • Keep schedule cadence aligned with your game activity and gas budget.

Verifying Randomness Through Wit/Oracle

The contract itself relies on Witnet's interface guarantees through UsingWitRandomness. If you want to inspect or verify randomness offchain or from scripts, the configured appliance exposes methods such as:

  • isRandomized(blockNumber)
  • fetchRandomnessAfter(blockNumber)
  • fetchRandomnessAfterProof(blockNumber)
  • verifyRandomnessAfter(blockNumber, witnetRandomness)

That is the core trust model: WitnetRoulette reads results from the appliance, and the appliance provides the verifiable path back to the underlying Wit/Oracle randomness source.

Constructor Parameters

WitnetRoulette is deployed with three parameters:

  1. randomizer The deployed Witnet randomness appliance address.
  2. roundIntervalBlocks The spacing, in blocks, between valid roulette rounds.
  3. ownerClaimDelayRounds The delay applied before the house can claim winnings from a round that resolved to 0.

Current constructor rules:

  • roundIntervalBlocks > 0
  • ownerClaimDelayRounds > 0

In the deployed contract, the delay is stored as:

  • ROUND_INTERVAL_BLOCKS
  • HOUSE_CLAIM_DELAY_ROUNDS

Deployment

The deploy script is scripts/deploy.ts.

It expects:

  • ROUND_INTERVAL_BLOCKS (default: 10)
  • HOUSE_CLAIM_DELAY_ROUNDS (default: 1)

If either value is not set, the deploy script prompts for it interactively.

  • Press Enter on ROUND_INTERVAL_BLOCKS to use 10.
  • Press Enter on HOUSE_CLAIM_DELAY_ROUNDS to use 1.

Before deploying, the script reads addresses.json and checks the WitnetRoulette entry for the target network.

  • If a non-zero address already exists, it asks whether to redeploy.
  • If you answer no, deployment is canceled and addresses.json is unchanged.
  • If you answer yes, deployment continues and addresses.json is updated with the new address.

RANDOMIZER_ADDRESS is only required on networks other than gnosis:testnet and gnosis:mainnet. On those two networks, the deploy and verify scripts resolve the Witnet randomness appliance address from the installed @witnet/solidity package.

Example:

$env:RANDOMIZER_ADDRESS="0xYourWitnetRandomnessAppliance"
$env:ROUND_INTERVAL_BLOCKS="10"
$env:HOUSE_CLAIM_DELAY_ROUNDS="1"
npm.cmd run hh:deploy:local

Gnosis examples:

$env:DEPLOYER_PRIVATE_KEY="0xyourPrivateKey"
$env:GNOSIS_TESTNET_RPC_URL="https://rpc.chiadochain.net"
$env:ROUND_INTERVAL_BLOCKS="10"
$env:HOUSE_CLAIM_DELAY_ROUNDS="1"
npm.cmd run hh:deploy:gnosis:testnet
$env:DEPLOYER_PRIVATE_KEY="0xyourPrivateKey"
$env:GNOSIS_MAINNET_RPC_URL="https://rpc.gnosischain.com"
$env:ROUND_INTERVAL_BLOCKS="10"
$env:HOUSE_CLAIM_DELAY_ROUNDS="1"
npm.cmd run hh:deploy:gnosis:mainnet

To verify a deployed contract, set CONTRACT_ADDRESS and run the verify script on the same network. The script defaults to Blockscout on Gnosis networks.

$env:CONTRACT_ADDRESS="0xYourDeployedRoulette"
npm.cmd run hh:verify -- --network gnosis:testnet

Optionally set VERIFY_PROVIDER to blockscout, etherscan, or sourcify.

Local Development

Contract workflow

  • Compile contracts:
npm.cmd run hh:compile
  • Run tests:
npm.cmd run hh:test
  • Start a local Hardhat node:
npm.cmd run hh:node
  • Deploy the contract:
npm.cmd run hh:deploy:local

Frontend workflow

The frontend is still available if you want to build a UI around the roulette contract:

  • Start dev server:
npm.cmd run dev
  • Build frontend:
npm.cmd run build
  • Preview build:
npm.cmd run preview

Test Coverage

The current test suite exercises the contract around the active-consumer settlement model, including:

  • deployment constraints
  • immutable configuration values
  • owner claim delay behavior
  • owner vs non-owner claim restrictions
  • pending liabilities interaction
  • future-round claim rejection with explicit revert reason

Run the suite with:

npm.cmd run hh:test

Repository Layout

Practical Takeaway

If you want to actively consume verifiable randomness from the Wit/Oracle Framework in an onchain game, the pattern shown here is:

  1. Integrate UsingWitRandomness.
  2. Bind your contract to a valid Witnet randomness appliance at deployment.
  3. Express game state in terms of future blocks or rounds.
  4. Refuse settlement until the relevant round is certified as randomized.
  5. Derive outcomes exclusively from Witnet-certified randomness.
  6. Keep operational infrastructure in place so the randomness appliance is being actively served with requests.

That is the design center of this repository.

About

Round-based roulette dApp and Solidity contract that consumes verifiable randomness from Witnet

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors