DEV Community

Cover image for CoinFlip: A Smart Contract game built with Soroban and React
simusud
simusud

Posted on

CoinFlip: A Smart Contract game built with Soroban and React

In this article, I will describe the steps to build a coin flip web app using Soroban and React.

GitHub Repo

Prerequisites

The first step you have to do is install Rust. You can follow the steps to install Rust in the following article:

  1. Node v18 - Install here: https://nodejs.org/en/download
  2. Rust - How to install Rust: https://soroban.stellar.org/docs/getting-started/setup#install-rust

  3. Soroban CLI - How to install Soroban CLI: https://soroban.stellar.org/docs/getting-started/setup#install-the-soroban-cli

  4. Stellar Account with test tokens on TEstnet - How to create new wallet using soroban-cli & receive test tokens: https://soroban.stellar.org/docs/getting-started/setup#configure-an-identity

  5. Freighter Wallet - Wallet extension for interact with the app. Link: https://www.freighter.app

Clone, Build, and Deploy Smart contract

  1. Clone the repository:
    git clone https://github.com/simusud/coin-flip.git

  2. Go to contracts/coin_flip

  3. Build the contract:
    soroban contract build

  4. Deploy the contract to Testnet:

soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/coin_flip.wasm \
--source alice \
--network testnet
Enter fullscreen mode Exit fullscreen mode

After the deployment is complete, you will receive a Contract Address. Save that address to be used in calling the contract functions.
The Contract Address will begin with C..... and looks like this CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC.

Calling Smart Contract Function

  1. Contract Initialization
pub fn initialize(env:Env, admin:Address) {
    if Self::has_administrator(env.clone()) {
        panic!("Contract already initialized.")
    }
    let key = DataKeys::Admin;
    env.storage().instance().set(&key, &admin)
}
Enter fullscreen mode Exit fullscreen mode

This function is called to initialize the contract with admin address as param. Upon initialization it sets a admin address

After deployment, the first function we should call is initialization. Call initialize function through CLI.

soroban contract invoke \
--id your_contract_id \
--source alice \
--network testnet \
-- \
initialize \
--admin alice 
Enter fullscreen mode Exit fullscreen mode
  1. Set native coin address
pub fn set_native_coin(env:Env, address:Address) {
    let admin = Self::get_admin(env.clone());
    admin.require_auth();

    let key = DataKeys::NativeCoinAddress;
    env.storage().instance().set(&key, &address)
}
Enter fullscreen mode Exit fullscreen mode

This function sets a native coin address. It can be called only by the admin.
The native coin (XLM) address in Soroban is : CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC

Set the XLM contact address through CLI.

soroban contract invoke \
 --id your_contract_id \
 --source alice \
 --network testnet \
 -- \
 set_native_coin \
 --address CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC 
Enter fullscreen mode Exit fullscreen mode
  1. Add XLM token to your contract The contract needs to hold double the amount of the flip. So we need to transfer some XLM to the contract depending upon the maximum limit a player can bet.
shell
   soroban contract invoke \
    --id CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC \
    --source alice \
    --network testnet \
    -- \
    transfer \
    --from alice \
    --to your_contract_id \
    --amount 1000000000 
Enter fullscreen mode Exit fullscreen mode
  1. Call flip function This is a public function and can be called by any user.

It first checks the balance of the contract if it is sufficient to pay back the double of the flip amount, if not it reverts the transaction.

let contract_balance = Self::get_balance_of_contract(env.clone());
        if contract_balance < (amount * 2) {
            panic!("Not enough balance in contract. Plese flip for lesser amount")
        }
Enter fullscreen mode Exit fullscreen mode

Then, it transfers the the amount of bet native coin to the contract itself from the user calling the transaction.

// transfer native coin to current contract from 'sender' address
        let native_coin_addr = Self::get_native_coin(env.clone());
        let flip_key = DataKeys::FlipCount;
        let flip_count: u32 = env.storage().instance().get::<DataKeys, u32>(&flip_key).unwrap_or(0);

        let native_coin_client = token::Client::new(&env, &native_coin_addr);
        native_coin_client.transfer(&sender, &env.current_contract_address(), &(amount));
Enter fullscreen mode Exit fullscreen mode

Then, it generates the random flip choice by shuffling the list containing head and tail items.

let value = vec![
            &env, 
            String::from_slice(&env, "tail"),
            String::from_slice(&env, "head"),
             ];

    let random_side = value.shuffle();
    let result = random_side.first_unchecked();
Enter fullscreen mode Exit fullscreen mode

It now checks the flip choice given by the sender and the choice randomly generated by the contact. If the sender's choice matches then contract sends double the bet amount to sender. And if sender's flip choice doesn't match the choice generated by contract it emits an event saying "You Lost".

if result == flip_choice {
            native_coin_client.transfer(&env.current_contract_address(), &sender, &(2 * amount));
            win_status = true;
            env.events().publish((sender.clone(), flip_choice.clone()), "You Won");
        } else {
            env.events().publish((sender, flip_choice.clone()), "You Lost");
        }
Enter fullscreen mode Exit fullscreen mode

It also updates the flip counter to tract the number of flips made in the app.

// increase flip counter 
        env.storage().instance().set(&flip_key, &(flip_count + 1));
Enter fullscreen mode Exit fullscreen mode

And there are some getter functions to get addresses of admin and native coin and native coin balance of the contract.

Build, deploy & run the app frontend

  1. Clone this repository:
    git clone https://github.com/simusud/coin-flip.git

  2. Go to frontend/coin_flip

  3. Change the contractAddress value to the contract address that you deployed in FlipOptions.tsx file in line no. 52.

  4. Run the app
    npm run dev

You will set the localhost where the app will run. Open the link.

  1. Click on Connect Wallet Botton on the top right corner of the web app.Then enter the password.

Image description

  1. Enter amount to flip, select flip choice and click Toss button to flip the coin.

Image description
And then wallet will pop up to approve the transaction and click Approve.

Now if you win you will receive double the amount you tossed.

Conclusion

Coin flip is a smart contract game on Soroban Testnet. This app is created for fun. I request all users to use it responsibly will the knowledge of risks of this in mainnet. Happy Tossing!

Top comments (0)