├── test └── .gitkeep ├── .gitattributes ├── .soliumrc.json ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── contracts ├── Migrations.sol └── Coinflip.sol ├── package.json └── truffle-config.js /test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": ["security"], 4 | "rules": { 5 | "no-call-value": "off" 6 | } 7 | } -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const Coinflip = artifacts.require("Coinflip"); 2 | 3 | module.exports = function (deployer, network, accounts) { 4 | let vrfCoodinatorAddress = "0x8C7382F9D8f56b33781fE506E897a4F1e2d17255"; 5 | let linkAddress="0x326C977E6efc84E512bB9C30f76E30c160eD06FB"; 6 | let fee=web3.utils.toWei("0.0001", "ether"); 7 | let keyHash ="0x6e75b569a01ef56d18cab6a8e71e6600d6ce853834d4a5748b720d06f878b3a4"; 8 | deployer.deploy(Coinflip, vrfCoodinatorAddress, linkAddress, fee, keyHash, {value: web3.utils.toWei("2", "ether"), from: accounts[0]}); 9 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flip-contract", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@chainlink/contracts": "^0.4.0", 14 | "@openzeppelin/contracts": "^4.5.0", 15 | "dotenv": "^16.0.0", 16 | "truffle": "^5.5.6", 17 | "truffle-plugin-verify": "^0.5.24" 18 | }, 19 | "devDependencies": { 20 | "@truffle/hdwallet-provider": "^2.0.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const privateKeys = process.env.PRIVATE_KEYS || "" 3 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 4 | module.exports = { 5 | networks: { 6 | development: { 7 | host: "127.0.0.1", // Localhost (default: none) 8 | port: 8545, // Standard Ethereum port (default: none) 9 | network_id: "*", // Any network (default: none) 10 | }, 11 | matic: { 12 | provider: () => new HDWalletProvider( 13 | privateKeys.split(','), 14 | `https://rpc-mumbai.maticvigil.com` 15 | ), 16 | network_id: 80001, 17 | confirmations: 2, 18 | networkCheckTimeout: 1000000, 19 | timeoutBlocks: 200, 20 | skipDryRun: true 21 | }, 22 | rinkeby: { 23 | provider: () => new HDWalletProvider( 24 | privateKeys.split(','), 25 | `https://rinkeby.infura.io/v3/23b8935c1649490bb448e80796e96581` 26 | ), 27 | network_id: 4, 28 | confirmations: 2, 29 | timeoutBlocks: 200, 30 | skipDryRun: true 31 | }, 32 | }, 33 | 34 | // Set default mocha options here, use special reporters etc. 35 | mocha: { 36 | // timeout: 100000 37 | }, 38 | 39 | //Set custom directory 40 | // contracts_directory: './src/contracts/', 41 | // contracts_build_directory: './src/abis/', 42 | 43 | // Configure your compilers 44 | compilers: { 45 | solc: { 46 | version: "0.8.4", // Fetch exact version from solc-bin (default: truffle's version) 47 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 48 | // settings: { // See the solidity docs for advice about optimization and evmVersion 49 | // optimizer: { 50 | // enabled: false, 51 | // runs: 200 52 | // }, 53 | // evmVersion: "byzantium" 54 | // } 55 | } 56 | }, 57 | 58 | plugins: [ 59 | 'truffle-plugin-verify' 60 | ], 61 | 62 | api_keys: { 63 | polygonscan: process.env.POLYGONSCAN_API_KEY, 64 | etherscan: process.env.RINKEBY_API_KEY 65 | }, 66 | 67 | db: { 68 | enabled: false, 69 | // host: "127.0.0.1", 70 | // adapter: { 71 | // name: "sqlite", 72 | // settings: { 73 | // directory: ".db" 74 | // } 75 | // } 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /contracts/Coinflip.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.4; 4 | 5 | import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | 9 | contract Coinflip is Ownable, VRFConsumerBase { 10 | 11 | using SafeMath for uint256; 12 | 13 | uint public contractBalance; 14 | 15 | struct Bet { 16 | address playerAddress; 17 | uint betValue; 18 | uint headsTails; 19 | } 20 | 21 | mapping (bytes32 => Bet) public bets; 22 | mapping (address => uint) public playerWinnings; 23 | 24 | event BetPlaced(bytes32 indexed id, address indexed player, uint256 amount, uint headsTails); 25 | event BetResult(bytes32 indexed id, address indexed player, uint256 amount, uint result); 26 | event userWithdrawal(address indexed caller, uint256 amount); 27 | 28 | bytes32 internal keyHash; 29 | uint256 internal fee; 30 | 31 | /// @notice set the necessary address, and value for Chainlink's VRF, set initial contract balance. 32 | /// @dev Params from _coorAddress to _keyHash necessary for Chainlink's VRF. 33 | constructor( address _coorAddress, address _linkAddress, uint256 _fee, bytes32 _keyHash ) VRFConsumerBase ( 34 | _coorAddress, 35 | _linkAddress 36 | ) payable { 37 | contractBalance = msg.value; 38 | fee = _fee; 39 | keyHash = _keyHash; 40 | } 41 | 42 | /// @notice start the bet 43 | /// @param oneZero - The numerical value of heads(0) or tails(1) 44 | function placeBet(uint256 oneZero) public payable { 45 | 46 | require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet"); 47 | require(contractBalance > msg.value, "We don't have enough funds"); 48 | 49 | bytes32 requestId = getRandomNumber(); 50 | 51 | bets[requestId] = Bet(msg.sender, msg.value, oneZero); 52 | emit BetPlaced(requestId, msg.sender, msg.value, oneZero); 53 | 54 | } 55 | 56 | /// @notice Add extra obscurity regarding the random number. 57 | /// @dev Remove and incorporate a simple uint variable to save gas. 58 | /// Call in the placeBet function. 59 | function getRandomNumber() internal returns(bytes32 requestId) { 60 | return requestRandomness(keyHash, fee); 61 | } 62 | 63 | /// @notice Chainlink's VRF returns a call to this function with the requestId and random 64 | /// number 65 | /// @dev Validate who is the winner. 66 | /// @param _requestId The return value used to track the VRF call with the returned uint 67 | /// @param _randomness The verifiable random number returned from Chainlink's VRF API 68 | function fulfillRandomness(bytes32 _requestId, uint256 _randomness) internal override { 69 | uint result = _randomness % 2; 70 | Bet memory bet = bets[_requestId]; 71 | uint256 payoutAmt = payout(bet, result); 72 | emit BetResult(_requestId, bet.playerAddress, payoutAmt, result); 73 | } 74 | 75 | function payout(Bet memory bet, uint flipResult) private returns (uint256) { 76 | if (bet.headsTails != flipResult) { 77 | contractBalance = SafeMath.add(contractBalance, bet.betValue); 78 | } else { 79 | contractBalance = SafeMath.sub(contractBalance, bet.betValue); 80 | playerWinnings[bet.playerAddress] = SafeMath.add(playerWinnings[bet.playerAddress], SafeMath.mul(bet.betValue, 2)); 81 | } 82 | 83 | return bet.betValue; 84 | } 85 | 86 | function withdrawUserWinnings() public { 87 | require(playerWinnings[msg.sender] > 0, "No funds to withdraw"); 88 | uint toTransfer = playerWinnings[msg.sender]; 89 | playerWinnings[msg.sender] = 0; 90 | payable(msg.sender).transfer(toTransfer); 91 | emit userWithdrawal(msg.sender, toTransfer); 92 | } 93 | 94 | function getWinningsBalance() public view returns(uint){ 95 | return playerWinnings[msg.sender]; 96 | } 97 | 98 | /** 99 | *@notice The following functions are reserved for the owner of the contract. 100 | */ 101 | 102 | function fundContract() public payable onlyOwner { 103 | contractBalance = contractBalance.add(msg.value); 104 | } 105 | 106 | function fundWinnings() public payable onlyOwner { 107 | playerWinnings[msg.sender] = playerWinnings[msg.sender].add(msg.value); 108 | } 109 | 110 | function withdrawAll() public onlyOwner { 111 | uint toTransfer = contractBalance; 112 | contractBalance = 0; 113 | payable(msg.sender).transfer(toTransfer); 114 | } 115 | 116 | } --------------------------------------------------------------------------------