├── .babelrc ├── src ├── logos │ ├── eth.png │ ├── dice.webp │ ├── logo.png │ ├── dice_logo.png │ └── dice_rolling.gif ├── components │ ├── App.css │ ├── Navbar.js │ ├── Loading.js │ ├── Main.js │ └── App.js ├── index.js ├── serviceWorker.js └── contract │ └── BettingGame.sol ├── public ├── favicon.ico ├── manifest.json └── index.html ├── README.md ├── .gitignore └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "stage-3"] 3 | } 4 | -------------------------------------------------------------------------------- /src/logos/eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappuniversity/chainlink_betting_game/HEAD/src/logos/eth.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappuniversity/chainlink_betting_game/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/logos/dice.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappuniversity/chainlink_betting_game/HEAD/src/logos/dice.webp -------------------------------------------------------------------------------- /src/logos/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappuniversity/chainlink_betting_game/HEAD/src/logos/logo.png -------------------------------------------------------------------------------- /src/logos/dice_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappuniversity/chainlink_betting_game/HEAD/src/logos/dice_logo.png -------------------------------------------------------------------------------- /src/logos/dice_rolling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dappuniversity/chainlink_betting_game/HEAD/src/logos/dice_rolling.gif -------------------------------------------------------------------------------- /src/components/App.css: -------------------------------------------------------------------------------- 1 | /* Styles go here */ 2 | br { 3 | display: block; 4 | margin: -4px; 5 | } 6 | 7 | div { 8 | word-wrap: break-word; 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### ```"A Decentralized Betting Game powered by Chainlink Randomness."``` 2 | 3 | ### 🔧 Project Diagram 4 | ![Project workflow](https://i.gyazo.com/0d76efbda6fce78509eabb1f68c928da.png) 5 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Starter Kit", 3 | "name": "Dapp University Starter Kit", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import 'bootstrap/dist/css/bootstrap.css' 4 | import App from './components/App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chainlink_betting_game", 3 | "version": "0.1.0", 4 | "description": "Betting Game", 5 | "author": "github.com/xternet", 6 | "homepage": ".", 7 | "dependencies": { 8 | "babel-polyfill": "6.26.0", 9 | "babel-preset-env": "1.7.0", 10 | "babel-preset-es2015": "6.24.1", 11 | "babel-preset-stage-2": "6.24.1", 12 | "babel-preset-stage-3": "6.24.1", 13 | "babel-register": "6.26.0", 14 | "bootstrap": "4.5.2", 15 | "identicon.js": "^2.3.3", 16 | "react": "^16.13.1", 17 | "react-bootstrap": "1.3.0", 18 | "react-dom": "16.13.1", 19 | "react-scripts": "3.4.3", 20 | "web3": "1.3.0" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": "react-app" 30 | }, 31 | "browserslist": [ 32 | ">0.2%", 33 | "not dead", 34 | "not ie <= 11", 35 | "not op_mini all" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import dice_logo from '../logos/dice_logo.png'; 3 | 4 | class Navbar extends Component { 5 | 6 | render() { 7 | return ( 8 | 31 | ); 32 | } 33 | } 34 | 35 | export default Navbar; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | Dapp University 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/Loading.js: -------------------------------------------------------------------------------- 1 | import dice_rolling from '../logos/dice_rolling.gif'; 2 | import React, { Component } from 'react'; 3 | import eth from '../logos/eth.png'; 4 | import './App.css'; 5 | 6 | class Loading extends Component { 7 | 8 | render() { 9 | return ( 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 23 | logo 24 | 25 |
26 |   27 |

28 |
29 | 36 |
37 |
38 |  ETH 39 |
40 |
41 |
42 | 49 |     50 | 57 |
58 |
59 | {!this.props.balance ?
: 60 |
61 |
62 | MaxBet  63 |
64 |
65 | {Number(this.props.web3.utils.fromWei((this.props.maxBet).toString())).toFixed(5)} ETH  66 |
67 |

68 |
69 | MinBet($1)  70 |
71 |
72 | {Number(this.props.web3.utils.fromWei((this.props.minBet).toString())).toFixed(5)} ETH  73 |
74 |

75 |
76 | Balance  77 |
78 |
79 | {Number(this.props.web3.utils.fromWei((this.props.balance).toString())).toFixed(5)} ETH  80 |
81 |
82 | } 83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | ); 91 | } 92 | } 93 | 94 | export default Loading; -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import dice from '../logos/dice.webp'; 3 | import eth from '../logos/eth.png'; 4 | import './App.css'; 5 | 6 | class Main extends Component { 7 | 8 | render() { 9 | return ( 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 23 | logo 24 | 25 |
26 |   27 |

28 |
29 | this.props.onChange(e.target.value)} 35 | required 36 | /> 37 |
38 |
39 |  ETH 40 |
41 |
42 |
43 | 60 |     61 | 79 |
80 |
81 | {!this.props.balance ?
: 82 |
83 |
84 | MaxBet  85 |
86 |
87 | {Number(this.props.web3.utils.fromWei((this.props.maxBet).toString())).toFixed(5)} ETH  88 |
89 |

90 |
91 | MinBet($1)  92 |
93 |
94 | {Number(this.props.web3.utils.fromWei((this.props.minBet).toString())).toFixed(5)} ETH  95 |
96 |

97 |
98 | Balance  99 |
100 |
101 | {Number(this.props.web3.utils.fromWei((this.props.balance).toString())).toFixed(5)} ETH  102 |
103 |
104 | } 105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | ); 113 | } 114 | } 115 | 116 | export default Main; -------------------------------------------------------------------------------- /src/contract/BettingGame.sol: -------------------------------------------------------------------------------- 1 | /** Contract created based on: https://docs.chain.link/docs/get-a-random-number 2 | * Contract created only for educational purposes, by: github.com/dappuniversity 3 | * You will need testnet ETH and LINK. 4 | * - Rinkeby ETH faucet: https://faucet.rinkeby.io/ 5 | * - Rinkeby LINK faucet: https://rinkeby.chain.link/ 6 | */ 7 | 8 | /** !UPDATE 9 | * min. bet >= $1 in Ethereum 10 | * 11 | * ETH/USD price will be received from Chainlink Oracles prices feed aggregator. 12 | * more: https://docs.chain.link/docs/using-chainlink-reference-contracts. 13 | */ 14 | pragma solidity 0.6.6; 15 | 16 | import "https://raw.githubusercontent.com/smartcontractkit/chainlink/master/evm-contracts/src/v0.6/VRFConsumerBase.sol"; 17 | import "https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol"; /* !UPDATE, import aggregator contract */ 18 | 19 | contract BettingGame is VRFConsumerBase { 20 | 21 | /** !UPDATE 22 | * 23 | * assign an aggregator contract to the variable. 24 | */ 25 | AggregatorV3Interface internal ethUsd; 26 | 27 | uint256 internal fee; 28 | uint256 public randomResult; 29 | 30 | //Network: Rinkeby 31 | address constant VFRC_address = 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B; // VRF Coordinator 32 | address constant LINK_address = 0x01BE23585060835E02B77ef475b0Cc51aA1e0709; // LINK token 33 | 34 | //declaring 50% chance, (0.5*(uint256+1)) 35 | uint256 constant half = 57896044618658097711785492504343953926634992332820282019728792003956564819968; 36 | 37 | //keyHash - one of the component from which will be generated final random value by Chainlink VFRC. 38 | bytes32 constant internal keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311; 39 | 40 | uint256 public gameId; 41 | uint256 public lastGameId; 42 | address payable public admin; 43 | mapping(uint256 => Game) public games; 44 | 45 | struct Game{ 46 | uint256 id; 47 | uint256 bet; 48 | uint256 seed; 49 | uint256 amount; 50 | address payable player; 51 | } 52 | 53 | modifier onlyAdmin() { 54 | require(msg.sender == admin, 'caller is not the admin'); 55 | _; 56 | } 57 | 58 | modifier onlyVFRC() { 59 | require(msg.sender == VFRC_address, 'only VFRC can call this function'); 60 | _; 61 | } 62 | 63 | event Withdraw(address admin, uint256 amount); 64 | event Received(address indexed sender, uint256 amount); 65 | event Result(uint256 id, uint256 bet, uint256 randomSeed, uint256 amount, address player, uint256 winAmount, uint256 randomResult, uint256 time); 66 | 67 | /** 68 | * Constructor inherits VRFConsumerBase. 69 | */ 70 | constructor() VRFConsumerBase(VFRC_address, LINK_address) public { 71 | fee = 0.1 * 10 ** 18; // 0.1 LINK 72 | admin = msg.sender; 73 | 74 | /** !UPDATE 75 | * 76 | * assign ETH/USD Rinkeby contract address to the aggregator variable. 77 | * more: https://docs.chain.link/docs/ethereum-addresses 78 | */ 79 | 80 | ethUsd = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e); 81 | } 82 | 83 | /* Allows this contract to receive payments */ 84 | receive() external payable { 85 | emit Received(msg.sender, msg.value); 86 | } 87 | 88 | 89 | /** !UPDATE 90 | * 91 | * Returns latest ETH/USD price from Chainlink oracles. 92 | */ 93 | function ethInUsd() public view returns (int) { 94 | (uint80 roundId, int price, uint startedAt, uint timeStamp, uint80 answeredInRound) = ethUsd.latestRoundData(); 95 | 96 | return price; 97 | } 98 | 99 | /** !UPDATE 100 | * 101 | * ethUsd - latest price from Chainlink oracles (ETH in USD * 10**8). 102 | * weiUsd - USD in Wei, received by dividing: 103 | * ETH in Wei (converted to compatibility with etUsd (10**18 * 10**8)), 104 | * by ethUsd. 105 | */ 106 | function weiInUsd() public view returns (uint) { 107 | int ethUsd = ethInUsd(); 108 | int weiUsd = 10**26/ethUsd; 109 | 110 | return uint(weiUsd); 111 | } 112 | 113 | /** 114 | * Taking bets function. 115 | * By winning, user 2x his betAmount. 116 | * Chances to win and lose are the same. 117 | */ 118 | function game(uint256 bet, uint256 seed) public payable returns (bool) { 119 | 120 | /** !UPDATE 121 | * 122 | * Checking if msg.value is higher or equal than $1. 123 | */ 124 | uint weiUsd = weiInUsd(); 125 | require(msg.value>=weiUsd, 'Error, msg.value must be >= $1'); 126 | 127 | //bet=0 is low, refers to 1-3 dice values 128 | //bet=1 is high, refers to 4-6 dice values 129 | require(bet<=1, 'Error, accept only 0 and 1'); 130 | 131 | //vault balance must be at least equal to msg.value 132 | require(address(this).balance>=msg.value, 'Error, insufficent vault balance'); 133 | 134 | //each bet has unique id 135 | games[gameId] = Game(gameId, bet, seed, msg.value, msg.sender); 136 | 137 | //increase gameId for the next bet 138 | gameId = gameId+1; 139 | 140 | //seed is auto-generated by DApp 141 | getRandomNumber(seed); 142 | 143 | return true; 144 | } 145 | 146 | /** 147 | * Request for randomness. 148 | */ 149 | function getRandomNumber(uint256 userProvidedSeed) internal returns (bytes32 requestId) { 150 | require(LINK.balanceOf(address(this)) > fee, "Error, not enough LINK - fill contract with faucet"); 151 | return requestRandomness(keyHash, fee, userProvidedSeed); 152 | } 153 | 154 | /** 155 | * Callback function used by VRF Coordinator. 156 | */ 157 | function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override { 158 | randomResult = randomness; 159 | 160 | //send final random value to the verdict(); 161 | verdict(randomResult); 162 | } 163 | 164 | /** 165 | * Send rewards to the winners. 166 | */ 167 | function verdict(uint256 random) public payable onlyVFRC { 168 | //check bets from latest betting round, one by one 169 | for(uint256 i=lastGameId; i=half && games[i].bet==1) || (random=amount, 'Error, contract has insufficent balance'); 196 | admin.transfer(amount); 197 | 198 | emit Withdraw(admin, amount); 199 | } 200 | } -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Loading from './Loading' 3 | import Navbar from './Navbar' 4 | import Main from './Main' 5 | import Web3 from 'web3' 6 | import './App.css'; 7 | 8 | class App extends Component { 9 | 10 | async componentWillMount() { 11 | await this.loadWeb3() 12 | } 13 | 14 | /** !UPDATE **/ 15 | async loadWeb3() { 16 | if(typeof window.ethereum!=='undefined' && !this.state.wrongNetwork){ 17 | let accounts, network, balance, web3, maxBet, minBet, contract, contract_abi, contract_address 18 | 19 | //don't refresh DApp when user change the network 20 | window.ethereum.autoRefreshOnNetworkChange = false; 21 | 22 | web3 = new Web3(window.ethereum) 23 | this.setState({web3: web3}) 24 | 25 | contract_abi = [{"anonymous": false,"inputs": [{"indexed": true,"internalType": "address","name": "sender","type": "address"},{"indexed": false,"internalType": "uint256","name": "amount","type": "uint256"}],"name": "Received","type": "event"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "id","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "bet","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "randomSeed","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "amount","type": "uint256"},{"indexed": false,"internalType": "address","name": "player","type": "address"},{"indexed": false,"internalType": "uint256","name": "winAmount","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "randomResult","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "time","type": "uint256"}],"name": "Result","type": "event"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "address","name": "admin","type": "address"},{"indexed": false,"internalType": "uint256","name": "amount","type": "uint256"}],"name": "Withdraw","type": "event"},{"inputs": [{"internalType": "uint256","name": "bet","type": "uint256"},{"internalType": "uint256","name": "seed","type": "uint256"}],"name": "game","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "payable","type": "function"},{"inputs": [{"internalType": "bytes32","name": "requestId","type": "bytes32"},{"internalType": "uint256","name": "randomness","type": "uint256"}],"name": "rawFulfillRandomness","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "bytes32","name": "_keyHash","type": "bytes32"},{"internalType": "uint256","name": "_fee","type": "uint256"},{"internalType": "uint256","name": "_seed","type": "uint256"}],"name": "requestRandomness","outputs": [{"internalType": "bytes32","name": "requestId","type": "bytes32"}],"stateMutability": "nonpayable","type": "function"},{"stateMutability": "payable","type": "receive"},{"inputs": [{"internalType": "uint256","name": "random","type": "uint256"}],"name": "verdict","outputs": [],"stateMutability": "payable","type": "function"},{"inputs": [{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "withdrawEther","outputs": [],"stateMutability": "payable","type": "function"},{"inputs": [{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "withdrawLink","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [],"stateMutability": "nonpayable","type": "constructor"},{"inputs": [],"name": "admin","outputs": [{"internalType": "address payable","name": "","type": "address"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "ethInUsd","outputs": [{"internalType": "int256","name": "","type": "int256"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "gameId","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "uint256","name": "","type": "uint256"}],"name": "games","outputs": [{"internalType": "uint256","name": "id","type": "uint256"},{"internalType": "uint256","name": "bet","type": "uint256"},{"internalType": "uint256","name": "seed","type": "uint256"},{"internalType": "uint256","name": "amount","type": "uint256"},{"internalType": "address payable","name": "player","type": "address"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "lastGameId","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "bytes32","name": "","type": "bytes32"}],"name": "nonces","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "randomResult","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "weiInUsd","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"}] 26 | contract_address = '0x2FeF79F6b8777D4C13E2D7be422628A5B458b7ad' //rinkeby 27 | contract = new web3.eth.Contract(contract_abi, contract_address); 28 | accounts = await web3.eth.getAccounts() 29 | 30 | //Update the data when user initially connect 31 | if(typeof accounts[0]!=='undefined' && accounts[0]!==null) { 32 | balance = await web3.eth.getBalance(accounts[0]) 33 | maxBet = await web3.eth.getBalance(contract_address) 34 | minBet = await contract.methods.weiInUsd().call() 35 | this.setState({account: accounts[0], balance: balance, minBet: minBet, maxBet: maxBet}) 36 | } 37 | 38 | this.setState({ 39 | contract: contract, 40 | contractAddress: contract_address 41 | }) 42 | 43 | //Update account&balance when user change the account 44 | window.ethereum.on('accountsChanged', async (accounts) => { 45 | if(typeof accounts[0] !== 'undefined' && accounts[0]!==null){ 46 | balance = await web3.eth.getBalance(accounts[0]) 47 | maxBet = await web3.eth.getBalance(contract_address) 48 | minBet = await contract.methods.weiInUsd().call() 49 | 50 | this.setState({account: accounts[0], balance: balance, minBet: minBet, maxBet: maxBet}) 51 | } else { 52 | this.setState({account: null, balance: 0}) 53 | } 54 | }); 55 | 56 | //Update data when user switch the network 57 | window.ethereum.on('chainChanged', async (chainId) => { 58 | network = parseInt(chainId, 16) 59 | if(network!==4){ 60 | this.setState({wrongNetwork: true}) 61 | } else { 62 | if(this.state.account){ 63 | balance = await this.state.web3.eth.getBalance(this.state.account) 64 | maxBet = await this.state.web3.eth.getBalance(this.state.contractAddress) 65 | minBet = await this.state.contract.methods.weiInUsd().call() 66 | 67 | this.setState({ balance: balance, maxBet: maxBet, minBet: minBet }) 68 | } 69 | this.setState({ network: network, loading: false, onlyNetwork: false, wrongNetwork: false}) 70 | } 71 | }); 72 | } 73 | } 74 | 75 | async makeBet(bet, amount) { 76 | //randomSeed - one of the components from which will be generated final random value 77 | const networkId = await this.state.web3.eth.net.getId() 78 | if(networkId!==4) { 79 | this.setState({wrongNetwork: true}) 80 | } else if(typeof this.state.account !=='undefined' && this.state.account !==null){ 81 | var randomSeed = Math.floor(Math.random() * Math.floor(1e9)) 82 | 83 | //Send bet to the contract and wait for the verdict 84 | this.state.contract.methods.game(bet, randomSeed).send({from: this.state.account, value: amount}).on('transactionHash', (hash) => { 85 | this.setState({ loading: true }) 86 | this.state.contract.events.Result({}, async (error, event) => { 87 | const verdict = event.returnValues.winAmount 88 | if(verdict === '0') { 89 | window.alert('lose :(') 90 | } else { 91 | window.alert('WIN!') 92 | } 93 | 94 | //Prevent error when user logout, while waiting for the verdict 95 | if(this.state.account!==null && typeof this.state.account!=='undefined'){ 96 | const balance = await this.state.web3.eth.getBalance(this.state.account) 97 | const maxBet = await this.state.web3.eth.getBalance(this.state.contractAddress) 98 | this.setState({ balance: balance, maxBet: maxBet }) 99 | } 100 | this.setState({ loading: false }) 101 | }) 102 | }).on('error', (error) => { 103 | window.alert('Error') 104 | }) 105 | } else { 106 | window.alert('Problem with account or network') 107 | } 108 | } 109 | 110 | onChange(value) { 111 | this.setState({'amount': value}); 112 | } 113 | 114 | constructor(props) { 115 | super(props) 116 | this.state = { 117 | account: null, 118 | amount: null, 119 | balance: null, 120 | contract: null, 121 | event: null, 122 | loading: false, 123 | network: null, 124 | maxBet: 0, 125 | minBet: 0, 126 | web3: null, 127 | wrongNetwork: false, 128 | contractAddress: null 129 | } 130 | 131 | this.makeBet = this.makeBet.bind(this) 132 | this.setState = this.setState.bind(this) 133 | this.onChange = this.onChange.bind(this) 134 | } 135 | 136 | render() { 137 | return ( 138 |
139 |   140 | {this.state.wrongNetwork 141 | ?
142 |
143 |

Please Enter Rinkeby Network

144 |
145 |
146 | : this.state.loading 147 | ? 153 | :
163 | } 164 |
165 | ); 166 | } 167 | } 168 | 169 | export default App; --------------------------------------------------------------------------------