Fair roulette is an example reference implementation which demonstrates the development, setup, and interaction with a smart contract.
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.
The mandatory setup consists out of:
- 1 GoShimmer node >= 0.7.5v (25c827e8326a)
- 1 Beta Wasp node.
- 1 Static file server (nginx, Apache, fasthttp)
Before you dive into the contents of the project, you should take a look at important 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.
This example uses On-ledger requests to initiate a betting request. A method to invoke Off-ledger requests is implemented inside the frontend.
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.
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.
The PoC consists of two projects residing in
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
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
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 ,
placed bets, and the
payout of the winners are published as events. Events are published as messages through a public web socket.
Building the Contract
The frontend has two main tasks.
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.
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
531819ion the number
2. Originating from the smart contract ID
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.
- 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
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:
The placeBetOnLedger function is responsible for sending On-Ledger bet requests. It constructs a simple OnLedger object containing:
- The smart contract ID:
- The function to invoke:
- An argument:
- 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
For Wasp, the address to send funds to is the chainId.
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:
Smart Contract: view_round_status
Since data returned by the views is encoded in Base64, the frontend needs to decode this by using simple
view_round_status view returns an
UInt16 which has a state of either
This means that to get a proper value from a view call, you should use
readUInt16LE to decode the matching value.
- NodeJS >= 14
If you use a different version of node, you can use nvm to switch node versions.
Go to your frontend directory ( contracts/wasm/fairroulette/frontend for example)
Install dependencies running:
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.
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
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 Frontend
You can build the frontend by running the following commands:
npm run build_worker
After this, you can run
npm run dev which will run a development server that exposes the transpiled frontend on
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.
You should follow the Deployment documentation until you reach the
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