Programming Project - Phase 1

Hey @CaliCrypto22

In the screenshot below, you can see that I would like the amount that they just bet to show up on the screen (after they have sent their bet amount to the smart contract).

If the user selects an amount to be and sends the transaction to the contract, he probably clicks a button. In that case you can just display the amount the user bet as soon as the user clicks the button and sends the transaction.

This is pretty easy to grasp.

You might have to think a bit more if you want to update the user balance on the screen as soon as the game is completed and you know if the user won or lost.

In order to do this you can use events: FAQ - How to listen for events

Whenever you get an event back, you will call a function in your smart contract that returns the user balance.

Regards,
Dani

Ready for phase two :slight_smile:

@dan-i
Withdraw is working now. Not only did I have to change to .send() in main.js but also to change msg.sender.transfer(address(this).balance); to msg.sender.transfer(contractBalance); in coinflip.sol
Then also, if someone runs into weird issues: You have to update the abi.js file every time you make even the slightest change to the .sol file and migrate --reset! Very important!

@dan-i: I found out. Syntax has to be: JSON.stringify(objectreference.returnValues.result);

QuestionJSONStringifyFormat

@dan-i
I managed to catch the result emitted by the event. I figured out that returned string is an object and found out how to get the ā€œreturnValuesā€. This is my result:

How can I get this to a better format?

Hey again @dan-i

I am currently at the withdraw function. The example you made is very useful, but I cannot figure out how to build a withdraw function.

In my Betting.sol file this is roughly similar structure of the withdraw function.

  struct PlayerInfo {
      uint balance;
      uint betAmount;
      uint choice;
      address playerAddress;
      bool inGame;
  }

  mapping(address => PlayerInfo) playerInfos;

  function withdrawUserBalance() public {
      require(msg.sender != address(this), "Cannot withdraw to contract");
      require(playerInfos[msg.sender].balance > 0, "No balance to withdraw");
      require(playerInfos[msg.sender].inGame = false, "Cannot withdraw while betting");
      uint toWithdraw = playerInfos[msg.sender].balance;

      playerInfos[msg.sender].balance = 0;
      contractBalance -= toWithdraw;    // tracker of the total contract balance
      msg.sender.transfer(toWithdraw);

      emit WithdrawalSuccessful(msg.sender, toWithdraw);
  }

I donā€™t understand how to write the withdraw functionality in main.js file. This is the (relevant) part of the code I have at the moment:

var web3 = new Web3(Web3.givenProvider);
const contractAddress = "0xEB4076457A12ABe28a2302BA1c621aC45c18D9ae";
var contractInstance;

$(document).ready(async() => {
    await window.ethereum.enable().then(function(accounts){
      contractInstance = new web3.eth.Contract(abi, contractAddress, {from: accounts[0]});
    });

    $("#deposit_button").click(fundThisContract);
    $("#getBalance_button").click(getContractBalance);
    $("#getUserInfo_button").click(getUserInfo);
    $("#withdraw_button").click(withdraw);
});

async function withdraw(){
  contractInstance.methods.withdrawUserBalance().call().send();
}

Happy to let you all know that I just finished Phase 1 of the CoinFlip Project. All functions work as intended and Unit Testing was successful!

You can check out my code here!

Here is my demo of the Coin Flip Phase 1: Video

Please note that in the video, the formatting of the result output is one version before final, in the final code, the formatting issue is solved. :partying_face: It is late over here though and I will go to bed now, haha. Maybe I will find some motivation to redo the demo video these daysand fix this cosmetical ā€œbugā€ :wink:

Hey @ZigaP

Letā€™s start with your function withdraw:

 function withdrawUserBalance() public {
      require(msg.sender != address(this), "Cannot withdraw to contract");
      require(playerInfos[msg.sender].balance > 0, "No balance to withdraw");
      require(playerInfos[msg.sender].inGame = false, "Cannot withdraw while betting");
      uint toWithdraw = playerInfos[msg.sender].balance;

      playerInfos[msg.sender].balance = 0;
      contractBalance -= toWithdraw;    // tracker of the total contract balance
      msg.sender.transfer(toWithdraw);

      emit WithdrawalSuccessful(msg.sender, toWithdraw);
  }

This require is not useful as this condition cannot be true:
require(msg.sender != address(this), "Cannot withdraw to contract");

When you compare values, make sure to use ==
require(playerInfos[msg.sender].inGame = false, "Cannot withdraw while betting");

Should be:
require(playerInfos[msg.sender].inGame == false, "Cannot withdraw while betting");

The rest of the function seems ok.

In your js file:

async function withdraw(){
  contractInstance.methods.withdrawUserBalance().call().send();
}

You either your .call() or .send() Documentation: https://web3js.readthedocs.io/en/v1.3.4/

In this case, try:

function withdraw(){
  contractInstance.methods.withdrawUserBalance().send({form:accounts[0]});
}

Make sure that accounts[0] is defined when you call that function.

Cheers,
Dani

1 Like

Ready for phase two!

1 Like

Thanks, works now! :slight_smile:

1 Like

Hello everyone,

There is something I didnā€™t grasp for the flip dapp. Where do we get a players address? The dapp is supposed to allow several players but where are the players address coming from? Do we use the addresses in Ganache?

1 Like

@dan-i @filip Hey guys,

Iā€™m making progress on the project. Iā€™ve made and tested the solidity side using remix so I am currently at the stage connecting the contract to the dapp.

While testing interaction between the dapp and the contract, I run into a problem:

I call getBalance() from main.js in order to retrieve and display the contract balance to ensure my contract constructor indeed takes 10 eth upon deployment. This works, however when I move on to use the contractā€™s placeBet() function, it gives me a type error saying itā€™s not a function? I tried testing this with random() function in the contract as well simply trying to see if I can return the contractā€™s result from random() in order to display it on the dapp but I receive the same errors.

Here are the console results when I click my Place Bet! then Refresh buttons:
Screen Shot 2021-03-22 at 3.43.55 PM

And here is my code from main.js and my betting.sol

var web3 = new Web3(Web3.givenProvider);
var contractInstance;
var bet;
var config;
var result;
var winnings;
var losses;

$(document).ready(function() {
    //brings up metamask ask for permission from browser, then passes accounts in metamask to promise function
    window.ethereum.enable().then(function(accounts) {
      //contract instance of abi template containing function definitions, address of contract, and default sender (metamask account 0)
      contractInstance = new web3.eth.Contract(abi, "0x0d68d691a00E878fD1793F8Af858537B2F5d1112", {from: accounts[0]});

      //update balance display
      contractInstance.methods.getBalance().call().then(function(balance) {
        $("#balance_output").text(web3.utils.fromWei(balance, "ether") + " ether");
        console.log("Balance: " + balance);
      })

      winnings = 0;
      losses = 0;

      $("#win_output").text(winnings);
      $("#lose_output").text(losses);

    });

    //on click of # buttons, call functions
    $("#bet_button").click(placeBet);
    $("#refresh_button").click(refresh);

});

//on add data button click,
function placeBet() {

  bet = $("#bet_input").val();

  console.log("bet button pressed. bet received from form is: " + bet);

  contractInstance.methods.placeBet().call().then(function(result) {
    console.log("placeBet() called! result: " + result);
  });

  contractInstance.methods.random().call().then(function(result) {
    console.log("random() called! result: "+ result);
  });
}

//get balance from contract to display
function refresh() {
  console.log("refresh button pressed");

  contractInstance.methods.getBalance().call().then(function(balance) {
    $("#balance_output").text(web3.utils.fromWei(balance, "ether") + " ether");
    console.log("Balance: " + balance);
  });

  contractInstance.methods.random().call().then(function(result) {
    console.log("result: " + result);
  });
}```

pragma solidity 0.5.12;

contract Betting {

uint public balance;
bool public win;

modifier costs(uint cost){
    require(msg.value >= cost, "Minimum bet or donation is 1 ether!");
    _;
}

modifier deploymentCosts(uint deploymentCost){
    require(msg.value >= deploymentCost, "Deployment costs 10 ether to initialize betting pot!");
    _;
}

modifier notEmpty() {
    require(balance > 0, "The pot is empty! Must wait for refill or donations!");
    _;
}

constructor() public payable deploymentCosts(10 ether) {
    require(msg.value >= 10 ether);

    balance += msg.value;
    win = false;
}

event betPlaced(uint bet, bool result, uint currentBalance);

//init pot to 10 ether
function init() public payable costs(10 ether) {
    require(msg.value >= 10 ether);
    balance += msg.value;
    win = false;
}

function placeBet() public payable costs(1 ether) notEmpty returns (uint) {
    //minimum bet is 1 ether
    //require(msg.value >= 1 ether);

    balance += msg.value;

    if(random() == 1) {
        win = true;
        balance -= msg.value*2;
        msg.sender.transfer(msg.value*2);
        emit betPlaced(msg.value, win, balance);

        return msg.value*2;
    } else {
        win = false;
        emit betPlaced(msg.value, win, balance);

        return 0;
    }

}

function getBalance() public view returns (uint){
    return balance;
}

function donate() public payable costs(1 ether) {
    balance += msg.value;
}

//returns pseudo random 1 or 0
function random() public view returns (uint) {
    return now % 2;
}

function testtest() public view returns (uint) {
  return 0;
}

}


Thanks for the help!

Iā€™ve tried contractInstance.methods. with the other functions in my contract as well and they all return the same error except for getBalance(). why is this?

I think I solved this - every time I make a change to the .sol contract, am I required to update the abi.js file with the new array from the contract build?

Also - Iā€™m using Brave browser and things started working once I cleared all browsing history, cookies, cache etc. Is this required every time I make a change? Is there a simpler way to do this?

Lastly, I am now able to call functions using contractInstance.methods.placeBet(), but I am getting this error when sending ETH to the contract:
Screen Shot 2021-03-23 at 1.48.05 AM

Is this something to do with using await? Or async in front of function?

Here is my .js code. For now Iā€™m simply trying to console.log and test sending to and receiving from the contract.

var web3 = new Web3(Web3.givenProvider);
var contractInstance;
var bet;
var config;
var result;
var winnings;
var losses;

$(document).ready(function() {
    //brings up metamask ask for permission from browser, then passes accounts in metamask to promise function
    window.ethereum.enable().then(function(accounts) {
      //contract instance of abi template containing function definitions, address of contract, and default sender (metamask account 0)
      contractInstance = new web3.eth.Contract(abi, "0x7961a7dDde5D3B90d4FA4f8c1eA1B62DAE275Ddf", {from: accounts[0]});

      //update balance display
      contractInstance.methods.getBalance().call().then(function(balance) {
        $("#balance_output").text(web3.utils.fromWei(balance, "ether") + " ether");
        console.log("Balance: " + balance);
      });

      //update balance display
      contractInstance.methods.getAddress().call().then(function(address) {
        $("#address_output").text(address);
        console.log("Sender address: " + address);
      });

      winnings = 0;
      losses = 0;

      $("#win_output").text(winnings);
      $("#lose_output").text(losses);

    });

    //on click of # buttons, call functions
    $("#bet_button").click(placeBet);
    $("#refresh_button").click(refresh);

});

//on add data button click,
function placeBet() {

  bet = $("#bet_input").val();

  console.log("bet button pressed. bet received from form is: " + bet);

  config = {
    value: web3.utils.toWei(bet, "ether")
  };

  contractInstance.methods.placeBet().send(config).then(async function(result) {
    if(result == 0) {
      console.log("lose")
    } else {
      console.log(result);
    }
  });
}

//get balance from contract to display
function refresh() {
  console.log("refresh button pressed");

  contractInstance.methods.getBalance().call().then(function(balance) {
    $("#balance_output").text(web3.utils.fromWei(balance, "ether") + " ether");
    console.log("Balance: " + balance);
  });

  contractInstance.methods.getAddress().call().then(function(result) {
    console.log("result: " + result);
  });

}

Hey @KlodyCodez

You can use the accounts in Ganache in order to simulate multiple players.
In a real dapp, you will have people interacting with your contract by using Metamask :slight_smile:

Let me know if you have questions.

Cheers,
Dani

1 Like

Hey @mervxxgotti

I think I solved this - every time I make a change to the .sol contract, am I required to update the abi.js file with the new array from the contract build?

If you modify the function name / the function parameter / the function return value and type you have to modify the ABI.
Take some time to read your abi and you will see why itā€™s important to have it updated :slight_smile:

Also - Iā€™m using Brave browser and things started working once I cleared all browsing history, cookies, cache etc. Is this required every time I make a change? Is there a simpler way to do this?

That should not be required but is surely a good try if something does not work in your front end or if changes made to your code are not reflected in your front end.

Lastly, I am now able to call functions using contractInstance.methods.placeBet(), but I am getting this error when sending ETH to the contract:

This is an issue with Metamask, follow these steps and let me know:

  1. Click the account icon on the top-right corner of MetaMask
  2. Select Settings
  3. Select Advanced
  4. Scroll down and click Reset Account

Cheers,
Dani

thank you Dani :slight_smile: youā€™re a life saver!

Hey @dan-i,

So my dapp and contract are functioning based on the requirements of making and placing bets. But I have some questions as there are some extra functionalities I still want to experiment with.

  1. When I place a bet using placeBet(), itā€™s working as expected, except for what it returns. In my .sol contract, I have it defined to return 0 or double the bet value in order to keep track of the results on the frontend. However, when I try to console.log() the returned result, I get the following (screenshot). How can I write this so on the frontend I can receive a uint?
    Screen Shot 2021-03-23 at 1.18.13 PM

  2. In my $(document).ready() function, I am able to get the metamask account balance and address, as well as the contract balance and address. I am using the refresh() function with the refresh button in order to retrieve updated account balances of both the contract and metamask accounts to check the results after placing a bet. However, this.web3.eth.getBalance() works in my document.ready() function but returns the following error when called from the refresh function. Why is this and how can I fix this?
    Screen Shot 2021-03-23 at 1.13.01 PM

Other than these two questions, everything is working!

Here is my code (Iā€™ve commented out the this.web3.eth error in the refresh() for functionality for now):

main.js

var web3 = new Web3(Web3.givenProvider);
var contractInstance;

var bet;
var config;
var result;

var winnings;
var losses;
var roundsWon;
var roundsLost;
var roundsPlayed;
var winFlag;

$(document).ready(function() {
    //brings up metamask ask for permission from browser, then passes accounts in metamask to promise function
    window.ethereum.enable().then(function(accounts) {
      //contract instance of abi template containing function definitions, address of contract, and default sender (metamask account 0)
      contractInstance = new web3.eth.Contract(abi, "0x7961a7dDde5D3B90d4FA4f8c1eA1B62DAE275Ddf", {from: accounts[0]});

      //Get player's Metamask account balance and address
      this.web3.eth.getBalance(accounts[0], (err, balance) => {
        balance = web3.utils.fromWei(balance, "ether");
        $("#metamask_balance_output").text(balance + " ETH");
        console.log("Metamask account balance: " + balance + " ETH");
      });

      contractInstance.methods.getSenderAddress().call().then(function(address) {
        $("#metamask_address_output").text(address);
        console.log("Metamask account (sender) address: " + address);
      });

      //Get Betting contract account balance and address
      contractInstance.methods.getBalance().call().then(function(balance) {
        balance = web3.utils.fromWei(balance, "ether");
        $("#contract_balance_output").text(balance + " ETH");
        console.log("Betting contract balance: " + balance + " ETH");
      });

      contractInstance.methods.getContractAddress().call().then(function(address) {
        $("#contract_address_output").text(address);
        console.log("Betting contract address: " + address);
      });

      $("#round_output").text("Unplayed!");

      winnings = 0;
      losses = 0;
      roundsWon = 0;
      roundsLost = 0;
      roundsPlayed = 0;

      $("#winnings_output").text(winnings);
      $("#losses_output").text(losses);
      $("#rounds_won_output").text(roundsWon);
      $("#rounds_lost_output").text(roundsLost);
      $("#rounds_played_output").text(roundsPlayed);


    });

    //on click of # buttons, call functions
    $("#bet_button").click(placeBet);
    $("#refresh_button").click(refresh);

});

//on add data button click,
function placeBet() {

  bet = $("#bet_input").val();

  console.log("***bet button pressed. bet received from form is: " + bet + " ***");

  config = {
    value: web3.utils.toWei(bet, "ether")
  };

  result = contractInstance.methods.placeBet().send(config).then(async function(result) {
    await console.log(result);
  });

  console.log(result);

}

//get balance from contract to display
function refresh() {
  console.log("***refresh button pressed***");

  /*
  this.web3.eth.getBalance(accounts[0], (err, balance) => {
    balance = web3.utils.fromWei(balance, "ether");
    $("#metamask_balance_output").text(balance + " ETH");
    console.log("Metamask account balance: " + balance + " ETH");
  });
  */

  //Get Betting contract account balance and address
  contractInstance.methods.getBalance().call().then(function(balance) {
    balance = web3.utils.fromWei(balance, "ether");
    $("#contract_balance_output").text(balance + " ETH");
    console.log("Betting contract balance: " + balance + " ETH");
  });

  contractInstance.methods.getWin().call().then(function(win) {

    if(win == true) {
      $("#round_output").text("You won the last round.");
      console.log("Previous round win flag: " + win);
    } else {
      $("#round_output").text("You lost the last round.");
      console.log("Previous round win flag: " + win);
    }
  });
}

Betting.sol

pragma solidity 0.5.12;

contract Betting {

    uint public balance;
    bool public win;

    modifier costs(uint cost){
        require(msg.value >= cost, "Minimum bet or donation is 1 ether!");
        _;
    }

    modifier deploymentCosts(uint deploymentCost){
        require(msg.value >= deploymentCost, "Deployment costs 10 ether to initialize betting pot!");
        _;
    }

    modifier notEmpty() {
        require(balance > 0, "The pot is empty! Must wait for refill or donations!");
        _;
    }

    constructor() public payable deploymentCosts(10 ether) {
        require(msg.value >= 10 ether);

        balance += msg.value;
        win = false;
    }

    event betPlaced(uint bet, bool result, uint currentBalance);

    //init pot to 10 ether
    function init() public payable costs(10 ether) {
        require(msg.value >= 10 ether);
        balance += msg.value;
        win = false;
    }

    function placeBet() public payable costs(1 ether) notEmpty returns (uint) {
        //minimum bet is 1 ether
        //require(msg.value >= 1 ether);

        balance += msg.value;

        //if random returns 1, user wins, set flag, transfer double of sent bet
        if(random() == 1) {
            win = true;
            balance -= msg.value*2;
            msg.sender.transfer(msg.value*2);

            emit betPlaced(msg.value, win, balance);

            return msg.value*2;

        //if random return 0, user loses, set flag, contract keeps bet
        } else {
            win = false;

            emit betPlaced(msg.value, win, balance);

            return 0;
        }
   }

    function getBalance() public view returns (uint){
        return balance;
    }

    function getSenderAddress() public view returns (address) {
      return msg.sender;
    }

    function getContractAddress() public view returns (address) {
        return address(this);
    }

    function getWin() public view returns (bool) {
      return win;
    }

    function donate() public payable costs(1 ether) {
        balance += msg.value;
    }

    //returns pseudo random 1 or 0
    function random() public view returns (uint) {
        return now % 2;
    }
}

Hi @mervxxgotti

When I place a bet using placeBet(), itā€™s working as expected, except for what it returns. In my .sol contract, I have it defined to return 0 or double the bet value in order to keep track of the results on the frontend. However, when I try to console.log() the returned result, I get the following (screenshot). How can I write this so on the frontend I can receive a uint?

The returned value a .send() function is always the receipt.
If you want to receive the uint you can emit an event and catch it with web3.
You can also save the result somewhere and get it with a .call function.

Regarding your 2nd question, please push your code to github and give the url, I will deploy and check :slight_smile:

Regards,
Dani

Thank you @dan-i I figured it out! To update the metamask account balance on the front end I simply have to move the window.ethereum.enable() into the function itself:

function refreshMetamaskBalance() {
  //Get player's Metamask account balance and address
  window.ethereum.enable().then(function(accounts) {

    this.web3.eth.getBalance(accounts[0], (err, balance) => {
      balance = web3.utils.fromWei(balance, "ether");
      $("#metamask_balance_output").text(balance + " ETH");
      console.log("Metamask account balance: " + balance + " ETH");
    });
  });
}

Could you explain to me why is this?

Also - could you elaborate more on how I would ā€œcatchā€ an event with web3 or "save the result and get it with a .call function? I did emit an event but Iā€™m not sure what you mean by catching it. Is this going into the logs to find values of payable functions because payable functions cannot return values?

Iā€™ve finished this phase of the project and here are my screenshots. Thank you so much for all your help!

0 init 1 place bet 5 2 results lose 3 place bet 10 4 results lose 5 place bet 15 6 results win

Hey @mervxxgotti

Regarding catching events, I wrote a basic FAQ that should clarify your doubts :slight_smile:
Check it out and let me know: FAQ - How to listen for events

About save the result and get it with a .call function what I mean is the following, let me give you an example:

pragma solidity 0.7.5;

contract Test {

    struct User {
        uint number;
    }
    
    mapping(address => User) public users;
    
    
    /**
     * Set number in User struct.
     * @param _n The number to save.
     */ 
    function setNumber (uint _n) public returns (uint) {
        users[msg.sender].number = _n;
        return users[msg.sender].number;
    } 
    
    /**
     * Get number in User struct.
     */ 
    function getNumber () public view returns (uint) {
        return users[msg.sender].number;
    }
}

From your front end, you would call:

instance.method.setNumber(10).send();

If you would save the returned value of the call above, you would end up with the transaction receipt.

So I created another function in the contract getNumber that just returns the number.

instance.method.getNumber().call();

You could do:

function test () {
    instance.method.setNumber(10).send();
    const result = instance.method.getNumber().call();
    console.log(result);
}

You should use events anyway, thatā€™s the right path to follow.

Cheers,
Dani