├── .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 | 
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 |
9 |
15 |
16 | B3tt1ng G@m3
17 |
18 | {!this.props.account ?
:
19 |
20 |
26 | {(this.props.account)}
27 |
28 |
29 | }
30 |
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 | You need to enable JavaScript to run this app.
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 |
26 |
27 |
28 |
29 |
36 |
37 |
38 |
ETH
39 |
40 |
41 |
42 |
{
46 | }}>
47 | Low
48 |
49 |
50 |
{
54 | }}>
55 | High
56 |
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 |
26 |
27 |
28 |
29 |
this.props.onChange(e.target.value)}
35 | required
36 | />
37 |
38 |
39 |
ETH
40 |
41 |
42 |
43 |
{
47 | event.preventDefault()
48 | //start with digit, digit+dot* or single dot*, end with digit.
49 | var reg = new RegExp("^[0-9]*.?[0-9]+$")
50 |
51 | if(reg.test(this.props.amount)){
52 | const amount = (this.props.amount).toString()
53 | this.props.makeBet(0, this.props.web3.utils.toWei(amount))
54 | } else {
55 | window.alert('Please type positive interger or float numbers')
56 | }
57 | }}>
58 | Low
59 |
60 |
61 |
{
65 | event.preventDefault()
66 | //start with digit, digit+dot* or single dot*, end with digit.
67 | var reg = new RegExp("^[0-9]*.?[0-9]+$")
68 | var minBet = Number(this.props.web3.utils.fromWei((this.props.minBet).toString())).toFixed(5)
69 |
70 | if(reg.test(this.props.amount) && this.props.amount>=minBet){
71 | const amount = (this.props.amount).toString()
72 | this.props.makeBet(1, this.props.web3.utils.toWei(amount))
73 | } else {
74 | window.alert('Please make sure that:\n*You typed positive interger or float number\n* Typed value is >= than MinBet (not all ETH decimals visible)\n* You are using Rinkeby network')
75 | }
76 | }}>
77 | High
78 |
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;
--------------------------------------------------------------------------------