Skip to main content

Fair Roulette

Fair roulette is an example reference implementation which demonstrates the development, setup, and interaction with a smart contract.

Introduction

The Fair roulette example project is a simple betting game in which players can bet on a number within a certain range.

The game consists of many rounds in which the player will try to bet on the right number to win a share of the bet funds.

A round is running for a certain amount of time. In the example its 60 seconds. In this timeframe, incoming bets will be added to a list of bets. After 60 seconds have passed, a winning number will be randomly generated and all players who made the right guess will receive their share of the pot.

If no round is active when a bet gets placed, the round gets initiated immediately.

The random number is generated by the native randomness of the IOTA Smart Contracts consensus. It is unpredictable by anybody, including an individual validator node. Therefore the roulette is Fair.

Mandatory Setup

The mandatory setup consists out of:

Technicalities

Before you dive into the contents of the project, you should take a look at important fundamentals.

Fundamentals

Wasp is part of the IOTA ecosystem that enables the execution of smart contracts. These contracts run logic and are allowed to do state (change) requests towards the Tangle. You will need a GoShimmer node to be able to store state. It receives state change requests and, if valid, saves them onto the Tangle.

There are two ways to interact with smart contracts.

On Ledger Requests

See: On-ledger Requests

On-ledger requests are sent to GoShimmer nodes. Wasp periodically requests new On-ledger requests from GoShimmer nodes, and handles them accordingly. These messages are validated through the network and take some time to be processed.

Off Ledger Requests

See: Off-ledger Requests

Off-ledger requests are directly sent to Wasp nodes and do not require validation through GoShimmer nodes. They are therefore faster. However, they require an initial deposit of funds to a chain account as this account will initiate required On-ledger requests on behalf of the desired contract or player.

note

This example uses On-ledger requests to initiate a betting request. A method to invoke Off-ledger requests is implemented inside the frontend.

See: placeBetOffLedger

Funds

As these requests cost some fees, and to be able to bet with real tokens, the player will need a source of funds.

As the game runs on a testnet, you can request funds from the GoShimmer faucets inside the network.

See: How to Obtain Tokens From the Faucet

After you have acquired some funds, they will reside inside an address that is handled by a wallet.

For this PoC, we have implemented a very narrowed-down wallet that runs inside the browser itself, mostly hidden from the player.

In the future, we want to provide a solution that enables the use of Firefly or MetaMask as a secure external wallet.

Conclusion

To interact with a smart contract, you will need:

  • A Wasp node that hosts the contract
  • A GoShimmer node to interact with the tangle
  • Funds from a GoShimmer faucet
  • A client that invokes the contract by either an On Ledger request or Off Ledger request. In this example, the Frontend acts as the client.

Implementation

The PoC consists of two projects residing in contracts/wasm/fairroulette.

One is the smart contract itself. Its boilerplate was generated using the new Schema tool which is shipped with this beta release. The contract logic is written in Rust, but the same implementation can be achieved interchangeably with Golang which is demonstrated in the root folder and ./src.

The second project is an interactive frontend written in TypeScript, made reactive with the light Svelte framework. You can find it in the sub-folder ./frontend. This frontend sends On-ledger requests to place bets towards the fair roulette smart contract and makes use of the GoShimmer faucet to request funds.

The Smart Contract

See: Anatomy of a Smart Contract

As the smart contract is the only actor that is allowed to modify state in the context of the game, it needs to handle a few tasks such as:

  • Validating and accepting placed bets
  • Starting and ending a betting round
  • Generating a random winning number
  • Sending payouts to the winners
  • Emitting status updates through the event system

Any incoming bet will be validated. This includes the amount of tokens which have been bet and also the number on which the player bet on. For example, any number over 8 or under 1 will be rejected.

If the bet is valid and no round is active, the round state will be changed to 1, marking an active round. The bet will be the first of a list of bets.

A delayed function call will be activated which executes after 60 seconds.

This function is the payout function that generates a random winning number, and pays out the winners of the round. After this, the round state will be set to 0 indicating the end of the round.

If a round is already active, the bet will be appended to the list of bets and await processing.

All state changes such as the round started ,round ended, placed bets, and the payout of the winners are published as events. Events are published as messages through a public web socket.

Dependencies

Building the Contract

cd contracts/wasm/fairroulette
wasm-pack build

The Frontend

The frontend has two main tasks.

  1. Visualize the contract's state: This includes the list of all placed bets, if a round is currently active and how long it's still going. Any payouts will be shown as well, including a fancy animation in case the player has won. The player can also see his current available funds, his seed, and his current address.

    danger

    The seed is the key to your funds. We display the seed for demonstration purposes only in this PoC. Never share your seed with anyone under any circumstance.

  2. Enable the player to request funds and participate in the game by placing bets: This is done by showing the player a list of eight numbers, a selection of the amount of funds to bet, and a bet placing button.

    As faucet requests require minimal proof of work, the calculation happens inside a web worker to prevent freezing the browser UI.

    To provide the frontend with the required events, it subscribes to the public web socket of Wasp to receive state changes.

    These state change events look like this:

    vmmsg kUUCRkhjD4EGAxv3kM77ay3KMbo51UhaEoCr14TeVHc5 df79d138: fairroulette.bet.placed 2sYqEZ5GM1BnqkZ88yJgPH3CdD9wKqfgGKY1j8FYDSZb3ao5wu 531819 2

    This event displays a placed bet from the address 12sYqEZ5GM1BnqkZ88yJgPH3CdD9wKqfgGKY1j8FYDSZb3ao5wu, a bet of 531819i on the number 2. Originating from the smart contract ID df79d138.

    However, there is a bit more to the concept than to simply subscribe to a web socket and "perform requests".

The Communication Layer

On and Off Ledger requests have a predefined structure. They need to get encoded strictly and include a list of transactions provided by Goshimmer. They also need to get signed by the client using the private key originating from a seed.

Wasp uses the ExtendedLockedOutput message type, which enables certain additional properties such as:

  • A fallback address and a fallback timeout
  • Unlockable by AliasUnlockBlock (if address is of Misaddress type)
  • A time lock (execution after deadline)
  • A data payload for arbitrary metadata (size limits apply)

This data payload is required to act on smart contracts as it contains:

  • The smart contract ID to be selected
  • The function ID to be executed
  • A list of arguments to be passed into the function

As we do not expect contract and frontend developers to write their own implementation, we have separated the communication layer into two parts:

The Wasp Client

The wasp client is an example implementation of the communication protocol.

It provides:

  • A basic wallet functionality
  • Hashing algorithms
  • A web worker to provide proof of work
  • Construction of On/Off Ledger requests
  • Construction of smart contract arguments and payloads
  • Generation of seeds (including their private keys and addresses)
  • Serialization of data into binary messages
  • Deserialization of smart contract state

This wasp_client can be seen as a soon-to-be external library. For now, this is a PoC client library shipped with the project. However, in the future , we want to provide a library you can simply include in your project.

The Fairroulette Service

This service is meant to be a high-level implementation of the actual app. In other words: it's the service that app or frontend developers would concentrate on.

It does not construct message types, nor does it interact with GoShimmer directly. Besides subscribing to the web socket event system of Wasp, it does not interact directly with Wasp either. Such communications are handled by the wasp_client.

The fairroulette service is a mere wrapper around smart contract invocation calls. It accesses the smart contract state through the wasp_client and does minimal decoding of data.

Let's take a look into three parts of this service to make this more clear.

This service comprises two parts:

PlaceBetOnLedger

The placeBetOnLedger function is responsible for sending On-Ledger bet requests. It constructs a simple OnLedger object containing:

  • The smart contract ID: fairroulette
  • The function to invoke: placeBet
  • An argument: -number
    • this is the number the player would bet on, the winning number

This transaction also requires an address to send the request to, and also a variable amount of funds over 0i.

note

For Wasp, the address to send funds to is the chainId.

See: CoreTypes and Invoking

CallView

The callView function is responsible for calling smart contract view functions.

See: Calling a view

To give access to the smart contracts state, you can use view functions to return selected parts of the state.

In this use case, you can poll the state of the contract at the initial page load of the frontend. State changes that happen afterwards are published through the websocket event system.

You can find examples to guide you in building similar functions in:

Since data returned by the views is encoded in Base64, the frontend needs to decode this by using simple Buffer methods. The view_round_status view returns an UInt16 which has a state of either 0 or 1.

This means that to get a proper value from a view call, you should use readUInt16LE to decode the matching value.

Dependencies

  • NodeJS >= 14
    If you use a different version of node, you can use nvm to switch node versions.
  • NPM

Install Dependencies

  1. Go to your frontend directory ( contracts/wasm/fairroulette/frontend for example)

    cd  contracts/wasm/fairroulette/frontend
  2. Install dependencies running:

    npm install

Configuration

The frontend requires that you create a config file. You can copy the template from contracts/wasm/fairroulette/frontend/config.dev.sample.js, and rename it to config.dev.js inside the same folder.

cp config.dev.sample.js config.dev.js

Make sure to update the config values according to your setup.

The chainId is the chainId which gets defined after deploying a chain. You can get your chain id from your dashboard, or list all chains by running:

wasp-cli chain list

waspWebSocketUrl, waspApiUrl, and goShimmerApiUrl are dependent on the location of your Wasp and GoShimmer nodes. Make sure to keep the path of the waspWeb SocketUrl (/chain/%chainId/ws) at the end.

seed can be either undefined or a predefined 44 length seed. If seed is set to undefined a new seed will be generated as soon a user opens the page. A predefined seed will be set for all users. This can be useful for development purposes.

Building The Fronted

You can build the frontend by running the following commands:

cd contracts/wasm/fairroulette/frontend
npm run build_worker

After this, you can run npm run dev which will run a development server that exposes the transpiled frontend on http://localhost:5000.

If you want to expose the dev server to the public, it might be required to bind the server to any endpoint like HOST=0.0.0.0 PORT=5000 npm run dev.

Deployment

You should follow the Deployment documentation until you reach the deploy-contract command.

The deployment of a contract requires funds to be deposited to the chain. You can do this by executing the following command from the directory where your Wasp node was configured:

wasp-cli chain deposit IOTA:10000

Make sure to Build the contract before deploying it.

Now, you can deploy the contract with a wasmtime configuration.

wasp-cli chain deploy-contract wasmtime fairroulette "fairroulette"  contracts/wasm/fairroulette/pkg/fairroulette_bg.wasm