├── .soliumignore ├── app ├── robots.txt ├── favicon.ico ├── images │ ├── imp.png │ ├── mag.png │ ├── bg-eth.jpg │ ├── bg_1024.jpg │ ├── bg_2048.jpg │ ├── bg_2880.jpg │ ├── favicon.ico │ ├── identicon1.png │ ├── identicon2.png │ ├── identicon3.png │ ├── bg-eth-1024.jpg │ ├── icons-72x72.png │ ├── doge-donations.png │ ├── ether-donations.png │ ├── icons-144x144.png │ ├── icons-180x180.png │ ├── icons-192x192.png │ ├── bitcoin-donations.png │ ├── money_with_wings.png │ └── litecoin-donations.png ├── fonts │ └── roboto │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Thin.woff │ │ ├── Roboto-Thin.woff2 │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.woff │ │ └── Roboto-Regular.woff2 ├── styles │ ├── emoji.css │ └── main.css ├── scripts │ ├── blockies.js │ └── lotthereum.js ├── grid.html └── index.html ├── .gitignore ├── README.md ├── truffle ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── truffle.js ├── contracts │ ├── Mortal.sol │ ├── Migrations.sol │ ├── SafeMath.sol │ └── Lotthereum.sol └── test │ └── lotthereum.js ├── bower.json ├── .soliumrc.json ├── LICENSE ├── package.json └── gulpfile.js /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | Disallow: 5 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | truffle/build 3 | .tmp 4 | dist 5 | node_modules 6 | bower_components 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lotthereum 2 | Open source decentralized lottery based on Ethereum blockchain 3 | -------------------------------------------------------------------------------- /app/images/imp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/imp.png -------------------------------------------------------------------------------- /app/images/mag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/mag.png -------------------------------------------------------------------------------- /app/images/bg-eth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/bg-eth.jpg -------------------------------------------------------------------------------- /app/images/bg_1024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/bg_1024.jpg -------------------------------------------------------------------------------- /app/images/bg_2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/bg_2048.jpg -------------------------------------------------------------------------------- /app/images/bg_2880.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/bg_2880.jpg -------------------------------------------------------------------------------- /app/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/favicon.ico -------------------------------------------------------------------------------- /app/images/identicon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/identicon1.png -------------------------------------------------------------------------------- /app/images/identicon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/identicon2.png -------------------------------------------------------------------------------- /app/images/identicon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/identicon3.png -------------------------------------------------------------------------------- /app/images/bg-eth-1024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/bg-eth-1024.jpg -------------------------------------------------------------------------------- /app/images/icons-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/icons-72x72.png -------------------------------------------------------------------------------- /app/images/doge-donations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/doge-donations.png -------------------------------------------------------------------------------- /app/images/ether-donations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/ether-donations.png -------------------------------------------------------------------------------- /app/images/icons-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/icons-144x144.png -------------------------------------------------------------------------------- /app/images/icons-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/icons-180x180.png -------------------------------------------------------------------------------- /app/images/icons-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/icons-192x192.png -------------------------------------------------------------------------------- /app/images/bitcoin-donations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/bitcoin-donations.png -------------------------------------------------------------------------------- /app/images/money_with_wings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/money_with_wings.png -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /app/images/litecoin-donations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/images/litecoin-donations.png -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /app/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotthereum/source/HEAD/app/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /truffle/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /truffle/truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*", // Match any network id 7 | // from: "0xf86c5833419fd517e38179519ccf102155d48952" 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lotthereum", 3 | "private": true, 4 | "dependencies": { 5 | "jquery": "~2.1.1", 6 | "materialize": "~0.98.2", 7 | "async": "^2.5.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "~4.0.2", 11 | "mocha": "~3.4.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/emoji.css: -------------------------------------------------------------------------------- 1 | .em{height:1.5em;width:1.5em;background-position:center;background-repeat:no-repeat;background-size:contain;display:inline-block;vertical-align:middle}.em-money_with_wings{background-image:url("/images/money_with_wings.png")}.em-imp{background-image:url("/images/imp.png")}.em-mag{background-image:url("/images/mag.png")} 2 | -------------------------------------------------------------------------------- /truffle/contracts/Mortal.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | 4 | contract Owned { 5 | address owner; 6 | 7 | modifier onlyowner() { 8 | if (msg.sender == owner) { 9 | _; 10 | } 11 | } 12 | 13 | function Owned() { 14 | owner = msg.sender; 15 | } 16 | } 17 | 18 | 19 | contract Mortal is Owned { 20 | 21 | function kill() { 22 | if (msg.sender == owner) 23 | selfdestruct(owner); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /truffle/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var Mortal = artifacts.require("./Mortal.sol"); 2 | var Lotthereum = artifacts.require("./Lotthereum.sol"); 3 | 4 | module.exports = function(deployer) { 5 | hash = '0x93036b147316017199338e191dbff124b5358520517f23a4b38db9769850f4ca'; 6 | deployer.deploy(Mortal); 7 | deployer.link(Mortal, Lotthereum); 8 | // deployer.deploy(Lotthereum, 1, 20, 100000000000000000, 1000000000000000000, hash); 9 | deployer.deploy(Lotthereum); 10 | }; 11 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom-rules-filename": null, 3 | "rules": { 4 | "imports-on-top": true, 5 | "variable-declarations": true, 6 | "array-declarations": true, 7 | "operator-whitespace": true, 8 | "lbrace": true, 9 | "mixedcase": true, 10 | "camelcase": true, 11 | "uppercase": true, 12 | "no-with": true, 13 | "no-empty-blocks": true, 14 | "no-unused-vars": true, 15 | "double-quotes": true, 16 | "blank-lines": true, 17 | "indentation": true, 18 | "whitespace": true, 19 | "deprecated-suicide": true, 20 | "pragma-on-top": true 21 | } 22 | } -------------------------------------------------------------------------------- /truffle/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Lotthereum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "engines": { 4 | "node": ">=4" 5 | }, 6 | "devDependencies": { 7 | "babel-core": "^6.4.0", 8 | "babel-preset-es2015": "^6.3.13", 9 | "babel-register": "^6.5.2", 10 | "browser-sync": "^2.2.1", 11 | "del": "^2.2.0", 12 | "gulp": "^3.9.0", 13 | "gulp-autoprefixer": "^3.0.1", 14 | "gulp-babel": "^6.1.1", 15 | "gulp-cache": "^0.4.2", 16 | "gulp-cssnano": "^2.0.0", 17 | "gulp-eslint": "^3.0.0", 18 | "gulp-htmlmin": "^3.0.0", 19 | "gulp-if": "^2.0.2", 20 | "gulp-imagemin": "^3.0.1", 21 | "gulp-load-plugins": "^1.2.4", 22 | "gulp-plumber": "^1.0.1", 23 | "gulp-size": "^2.1.0", 24 | "gulp-sourcemaps": "^2.2.0", 25 | "gulp-uglify": "^2.0.0", 26 | "gulp-useref": "^3.0.0", 27 | "main-bower-files": "^2.5.0", 28 | "run-sequence": "^1.2.2", 29 | "wiredep": "^4.0.0" 30 | }, 31 | "eslintConfig": { 32 | "env": { 33 | "es6": true, 34 | "node": true, 35 | "browser": true, 36 | "jquery": true 37 | }, 38 | "rules": { 39 | "quotes": [ 40 | 2, 41 | "single" 42 | ] 43 | } 44 | }, 45 | "dependencies": {} 46 | } 47 | -------------------------------------------------------------------------------- /truffle/contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | 4 | contract SafeMath { 5 | 6 | // ensure that the result of adding x and y is accurate 7 | function add(uint x, uint y) internal constant returns (uint z) { 8 | assert((z = x + y) >= x); 9 | } 10 | 11 | // ensure that the result of subtracting y from x is accurate 12 | function subtract(uint x, uint y) internal constant returns (uint z) { 13 | assert((z = x - y) <= x); 14 | } 15 | 16 | // ensure that the result of multiplying x and y is accurate 17 | function multiply(uint x, uint y) internal constant returns (uint z) { 18 | z = x * y; 19 | assert(x == 0 || z / x == y); 20 | return z; 21 | } 22 | 23 | // ensure that the result of dividing x and y is accurate 24 | // note: Solidity now throws on division by zero, so a check is not needed 25 | function divide(uint x, uint y) internal constant returns (uint z) { 26 | z = x / y; 27 | assert(x == ( (y * z) + (x % y) )); 28 | return z; 29 | } 30 | 31 | // return the lowest of two 64 bit integers 32 | function min64(uint64 x, uint64 y) internal constant returns (uint64) { 33 | return x < y ? x: y; 34 | } 35 | 36 | // return the largest of two 64 bit integers 37 | function max64(uint64 x, uint64 y) internal constant returns (uint64) { 38 | return x >= y ? x : y; 39 | } 40 | 41 | // return the lowest of two values 42 | function min(uint x, uint y) internal constant returns (uint) { 43 | return (x <= y) ? x : y; 44 | } 45 | 46 | // return the largest of two values 47 | function max(uint x, uint y) internal constant returns (uint) { 48 | return (x >= y) ? x : y; 49 | } 50 | 51 | function assert(bool assertion) internal { 52 | if (!assertion) { 53 | revert(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/scripts/blockies.js: -------------------------------------------------------------------------------- 1 | // https://github.com/alexvandesande/blockies/blob/master/blockies.js 2 | // updated tayvano 3.9.16 3 | (function() { 4 | // The random number is a js implementation of the Xorshift PRNG 5 | var randseed = new Array(4); // Xorshift: [x, y, z, w] 32 bit values 6 | 7 | function seedrand(seed) { 8 | for (var i = 0; i < randseed.length; i++) { 9 | randseed[i] = 0; 10 | } 11 | for (var i = 0; i < seed.length; i++) { 12 | randseed[i%4] = ((randseed[i%4] << 5) - randseed[i%4]) + seed.charCodeAt(i); 13 | } 14 | } 15 | 16 | function rand() { 17 | // based on Java's String.hashCode(), expanded to 4 32bit values 18 | var t = randseed[0] ^ (randseed[0] << 11); 19 | 20 | randseed[0] = randseed[1]; 21 | randseed[1] = randseed[2]; 22 | randseed[2] = randseed[3]; 23 | randseed[3] = (randseed[3] ^ (randseed[3] >> 19) ^ t ^ (t >> 8)); 24 | 25 | return (randseed[3]>>>0) / ((1 << 31)>>>0); 26 | } 27 | 28 | function createColor() { 29 | //saturation is the whole color spectrum 30 | var h = Math.floor(rand() * 360); 31 | //saturation goes from 40 to 100, it avoids greyish colors 32 | var s = ((rand() * 60) + 40) + '%'; 33 | //lightness can be anything from 0 to 100, but probabilities are a bell curve around 50% 34 | var l = ((rand()+rand()+rand()+rand()) * 25) + '%'; 35 | 36 | var color = 'hsl(' + h + ',' + s + ',' + l + ')'; 37 | return color; 38 | } 39 | 40 | function createImageData(size) { 41 | var width = size; // Only support square icons for now 42 | var height = size; 43 | 44 | var dataWidth = Math.ceil(width / 2); 45 | var mirrorWidth = width - dataWidth; 46 | 47 | var data = []; 48 | for(var y = 0; y < height; y++) { 49 | var row = []; 50 | for(var x = 0; x < dataWidth; x++) { 51 | // this makes foreground and background color to have a 43% (1/2.3) probability 52 | // spot color has 13% chance 53 | row[x] = Math.floor(rand()*2.3); 54 | } 55 | var r = row.slice(0, mirrorWidth); 56 | r.reverse(); 57 | row = row.concat(r); 58 | 59 | for(var i = 0; i < row.length; i++) { 60 | data.push(row[i]); 61 | } 62 | } 63 | 64 | return data; 65 | } 66 | 67 | function createCanvas(imageData, color, scale, bgcolor, spotcolor) { 68 | var c = document.createElement('canvas'); 69 | var width = Math.sqrt(imageData.length); 70 | c.width = c.height = width * scale; 71 | 72 | var cc = c.getContext('2d'); 73 | cc.fillStyle = bgcolor; 74 | cc.fillRect(0, 0, c.width, c.height); 75 | cc.fillStyle = color; 76 | 77 | for(var i = 0; i < imageData.length; i++) { 78 | var row = Math.floor(i / width); 79 | var col = i % width; 80 | // if data is 2, choose spot color, if 1 choose foreground 81 | cc.fillStyle = (imageData[i] == 1) ? color : spotcolor; 82 | 83 | // if data is 0, leave the background 84 | if(imageData[i]) { 85 | cc.fillRect(col * scale, row * scale, scale, scale); 86 | } 87 | } 88 | 89 | return c; 90 | } 91 | 92 | function createIcon(opts) { 93 | opts = opts || {}; 94 | var size = opts.size || 8; 95 | var scale = opts.scale || 4; 96 | var seed = opts.seed || Math.floor((Math.random()*Math.pow(10,16))).toString(16); 97 | 98 | seedrand(seed); 99 | 100 | var color = opts.color || createColor(); 101 | var bgcolor = opts.bgcolor || createColor(); 102 | var spotcolor = opts.spotcolor || createColor(); 103 | var imageData = createImageData(size); 104 | var canvas = createCanvas(imageData, color, scale, bgcolor, spotcolor); 105 | 106 | return canvas; 107 | } 108 | 109 | window.blockies = {create: createIcon}; 110 | })(); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // generated on 2017-06-14 using generator-webapp 3.0.1 2 | const gulp = require('gulp'); 3 | const gulpLoadPlugins = require('gulp-load-plugins'); 4 | const browserSync = require('browser-sync').create(); 5 | const del = require('del'); 6 | const wiredep = require('wiredep').stream; 7 | const runSequence = require('run-sequence'); 8 | 9 | const $ = gulpLoadPlugins(); 10 | const reload = browserSync.reload; 11 | 12 | let dev = true; 13 | 14 | gulp.task('styles', () => { 15 | return gulp.src('app/styles/*.css') 16 | .pipe($.if(dev, $.sourcemaps.init())) 17 | .pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']})) 18 | .pipe($.if(dev, $.sourcemaps.write())) 19 | .pipe(gulp.dest('.tmp/styles')) 20 | .pipe(reload({stream: true})); 21 | }); 22 | 23 | gulp.task('scripts', () => { 24 | return gulp.src('app/scripts/**/*.js') 25 | .pipe($.plumber()) 26 | .pipe($.if(dev, $.sourcemaps.init())) 27 | .pipe($.babel()) 28 | .pipe($.if(dev, $.sourcemaps.write('.'))) 29 | .pipe(gulp.dest('.tmp/scripts')) 30 | .pipe(reload({stream: true})); 31 | }); 32 | 33 | function lint(files) { 34 | return gulp.src(files) 35 | .pipe($.eslint({ fix: true })) 36 | .pipe(reload({stream: true, once: true})) 37 | .pipe($.eslint.format()) 38 | .pipe($.if(!browserSync.active, $.eslint.failAfterError())); 39 | } 40 | 41 | gulp.task('lint', () => { 42 | return lint('app/scripts/**/*.js') 43 | .pipe(gulp.dest('app/scripts')); 44 | }); 45 | gulp.task('lint:test', () => { 46 | return lint('test/spec/**/*.js') 47 | .pipe(gulp.dest('test/spec')); 48 | }); 49 | 50 | gulp.task('html', ['styles', 'scripts'], () => { 51 | return gulp.src('app/*.html') 52 | .pipe($.useref({searchPath: ['.tmp', 'app', '.']})) 53 | .pipe($.if(/\.js$/, $.uglify({compress: {drop_console: true}}))) 54 | .pipe($.if(/\.css$/, $.cssnano({safe: true, autoprefixer: false}))) 55 | .pipe($.if(/\.html$/, $.htmlmin({ 56 | collapseWhitespace: true, 57 | minifyCSS: true, 58 | minifyJS: {compress: {drop_console: true}}, 59 | processConditionalComments: true, 60 | removeComments: true, 61 | removeEmptyAttributes: true, 62 | removeScriptTypeAttributes: true, 63 | removeStyleLinkTypeAttributes: true 64 | }))) 65 | .pipe(gulp.dest('dist')); 66 | }); 67 | 68 | gulp.task('images', () => { 69 | return gulp.src('app/images/**/*') 70 | .pipe($.cache($.imagemin())) 71 | .pipe(gulp.dest('dist/images')); 72 | }); 73 | 74 | gulp.task('fonts', () => { 75 | return gulp.src(require('main-bower-files')('**/*.{eot,svg,ttf,woff,woff2}', function (err) {}) 76 | .concat('app/fonts/**/*')) 77 | .pipe($.if(dev, gulp.dest('.tmp/fonts'), gulp.dest('dist/fonts'))); 78 | }); 79 | 80 | gulp.task('extras', () => { 81 | return gulp.src([ 82 | 'app/*', 83 | '!app/*.html' 84 | ], { 85 | dot: true 86 | }).pipe(gulp.dest('dist')); 87 | }); 88 | 89 | gulp.task('clean', del.bind(null, ['.tmp', 'dist'])); 90 | 91 | gulp.task('serve', () => { 92 | runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'fonts'], () => { 93 | browserSync.init({ 94 | notify: false, 95 | port: 9000, 96 | server: { 97 | baseDir: ['.tmp', 'app'], 98 | routes: { 99 | '/bower_components': 'bower_components' 100 | } 101 | } 102 | }); 103 | 104 | gulp.watch([ 105 | 'app/*.html', 106 | 'app/images/**/*', 107 | '.tmp/fonts/**/*' 108 | ]).on('change', reload); 109 | 110 | gulp.watch('app/styles/**/*.css', ['styles']); 111 | gulp.watch('app/scripts/**/*.js', ['scripts']); 112 | gulp.watch('app/fonts/**/*', ['fonts']); 113 | gulp.watch('bower.json', ['wiredep', 'fonts']); 114 | }); 115 | }); 116 | 117 | gulp.task('serve:dist', ['default'], () => { 118 | browserSync.init({ 119 | notify: false, 120 | port: 9000, 121 | server: { 122 | baseDir: ['dist'] 123 | } 124 | }); 125 | }); 126 | 127 | gulp.task('serve:test', ['scripts'], () => { 128 | browserSync.init({ 129 | notify: false, 130 | port: 9000, 131 | ui: false, 132 | server: { 133 | baseDir: 'test', 134 | routes: { 135 | '/scripts': '.tmp/scripts', 136 | '/bower_components': 'bower_components' 137 | } 138 | } 139 | }); 140 | 141 | gulp.watch('app/scripts/**/*.js', ['scripts']); 142 | gulp.watch(['test/spec/**/*.js', 'test/index.html']).on('change', reload); 143 | gulp.watch('test/spec/**/*.js', ['lint:test']); 144 | }); 145 | 146 | // inject bower components 147 | gulp.task('wiredep', () => { 148 | gulp.src('app/*.html') 149 | .pipe(wiredep({ 150 | ignorePath: /^(\.\.\/)*\.\./ 151 | })) 152 | .pipe(gulp.dest('app')); 153 | }); 154 | 155 | gulp.task('build', ['lint', 'html', 'images', 'fonts', 'extras'], () => { 156 | return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true})); 157 | }); 158 | 159 | gulp.task('default', () => { 160 | return new Promise(resolve => { 161 | dev = false; 162 | runSequence(['clean', 'wiredep'], 'build', resolve); 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | .browserupgrade { 2 | margin: 0.2em 0; 3 | background: #ccc; 4 | color: #000; 5 | padding: 0.2em 0; 6 | } 7 | 8 | body::before { 9 | will-change: transform; 10 | z-index: -1; 11 | left: 0; 12 | right: 0; 13 | bottom: 0; 14 | top: 0; 15 | position: fixed; 16 | background: #2196f3 !important; 17 | } 18 | 19 | body { 20 | background: #2196f3 !important; 21 | } 22 | 23 | .brand-logo { 24 | margin-left: 21px; 25 | margin-left: 0; 26 | color: red; 27 | } 28 | 29 | .logo { 30 | padding: 15px; 31 | width: 64px; 32 | } 33 | 34 | .btn-floating img { 35 | width: 100%; 36 | } 37 | 38 | .bet-btn { 39 | top: 15px !important; 40 | right: 15px !important; 41 | } 42 | 43 | .collection-item { 44 | background: rgba(255, 255, 255, 0) !important; 45 | } 46 | 47 | .collection { 48 | border: 0 !important; 49 | } 50 | 51 | .avatar { 52 | min-height: 65px !important; 53 | } 54 | 55 | .collection .collection-item.avatar .title { 56 | position: absolute; 57 | top: 21px; 58 | } 59 | 60 | .collection .collection-item.avatar .secondary-content { 61 | top: 21px !important; 62 | } 63 | 64 | .btn-bar { 65 | position: relative !important; 66 | } 67 | 68 | .nav-wrapper { 69 | background: #2196F3; 70 | /*background: white;*/ 71 | } 72 | 73 | .progress { 74 | margin-top: 15px !important; 75 | margin-bottom: 40px !important; 76 | } 77 | 78 | .round-data td { 79 | font-weight: 100 !important; 80 | } 81 | 82 | .round-data { 83 | border-top: 1px solid #e0e0e0; 84 | } 85 | 86 | .page-footer { 87 | /*display: none;*/ 88 | margin-top: 50px; 89 | background: #43c5ff !important; 90 | margin-top: 10px; 91 | } 92 | 93 | .footer-copyright { 94 | background: #2196f3 !important; 95 | } 96 | 97 | .eth-address, .eth-address2 { 98 | width: 42px; 99 | height: 42px; 100 | background-size: cover; 101 | background-repeat: no-repeat; 102 | border-radius: 50%; 103 | box-shadow: inset rgba(255, 255, 255, 0.6) 0 2px 2px, inset rgba(0, 0, 0, 0.3) 0 -2px 6px, 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 104 | /*box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);*/ 105 | text-indent: -9999px; 106 | position: absolute; 107 | left: 15px; 108 | } 109 | 110 | .pick h3 { 111 | margin-top: 7px !important; 112 | } 113 | 114 | .pick { 115 | margin: 5px; 116 | } 117 | 118 | #accounts_dropdown { 119 | width: 90% !important; 120 | margin-top: -9px; 121 | } 122 | 123 | .wait_transaction { 124 | font-weight: 100; 125 | margin-top: -33px; 126 | } 127 | 128 | .dropdown-content li > span { 129 | color: #e29f3d !important; 130 | } 131 | 132 | #confirmations { 133 | opacity: 0; 134 | } 135 | 136 | .confirmation-badge { 137 | position: absolute; 138 | left: 435px; 139 | top: 20px; 140 | } 141 | 142 | .template { 143 | display: none; 144 | } 145 | 146 | #loading { 147 | display: none; 148 | text-align: center; 149 | padding: 5%; 150 | } 151 | 152 | .round { 153 | margin-top: 30px !important; 154 | } 155 | 156 | .main-container { 157 | margin: 0 !important; 158 | width: 100% !important; 159 | max-width: 100% !important; 160 | padding: 20px 5% 10px 5% !important; 161 | background-color: white; 162 | display: none; 163 | } 164 | 165 | .pagination { 166 | text-align: center; 167 | } 168 | 169 | .tabs { 170 | width: 80% !important; 171 | z-index: 101; 172 | height: 56px !important; 173 | } 174 | 175 | .tabs, .tab, .tab a { 176 | height: 64px !important; 177 | line-height: 64px !important; 178 | background-color: #2196f3 !important; 179 | color: white !important; 180 | } 181 | 182 | .indicator { 183 | background-color: #ffb74d !important; 184 | } 185 | 186 | .avatar2 { 187 | position: absolute !important; 188 | top: 11px; 189 | right: 15px; 190 | left: auto; 191 | min-height: auto !important; 192 | width: 42px; 193 | height: 42px; 194 | } 195 | 196 | #avatar { 197 | display: none; 198 | cursor: pointer; 199 | } 200 | 201 | .number { 202 | padding-top: 4px; 203 | } 204 | 205 | .dropdown-content { 206 | width: 350px !important; 207 | background-color: rgba(255, 255, 255, 1) !important; 208 | padding: 20px 70px !important; 209 | } 210 | 211 | .dropdown-content li a { 212 | padding-left: 70px !important; 213 | font-size: 11px !important; 214 | } 215 | 216 | #dropdown-nav { 217 | padding: 0px !important; 218 | z-index: 1000 !important; 219 | top: 64px !important; 220 | right: 0px !important; 221 | } 222 | 223 | .square { 224 | width: 24px !important; 225 | height: 24px !important; 226 | } 227 | 228 | .navbar-fixed { 229 | /*display: none;*/ 230 | } 231 | 232 | .balance { 233 | display: none; 234 | top: 21px; 235 | position: relative; 236 | margin-left: 0px !important; 237 | } 238 | 239 | .modal-footer { 240 | padding: 0px 15px 15px !important; 241 | } 242 | 243 | .modal-content { 244 | padding-bottom: 0; 245 | } 246 | 247 | .card-content { 248 | min-height: 230px; 249 | } 250 | 251 | .tab.disabled a { 252 | color: #c1c1c1 !important; 253 | } 254 | 255 | #bet1 .modal-content { 256 | padding-bottom: 0px; 257 | margin-bottom: 0px; 258 | } 259 | 260 | #bet1 table { 261 | width: 95%; 262 | margin: auto; 263 | } 264 | 265 | .collection-header { 266 | background: none !important; 267 | background: transparent !important; 268 | } 269 | 270 | #intro .modal-content { 271 | padding: 0px; 272 | } 273 | 274 | #intro .modal-content .collection { 275 | margin-top: 0px; 276 | } 277 | 278 | .gitter-chat-embed { 279 | z-index: 1000; 280 | } 281 | 282 | .alert-note { 283 | display: none; 284 | } 285 | 286 | .navbar-fixed nav { 287 | z-index: 100; 288 | } 289 | 290 | .dropdown-content li > a, .dropdown-content li > span { 291 | width: 100% !important; 292 | } 293 | 294 | #bet-modal .modal-content { 295 | padding-top: 0 !important; 296 | } -------------------------------------------------------------------------------- /app/grid.html: -------------------------------------------------------------------------------- 1 | Lotthereum
play_arrow Round 323
3 bets remaining
  • 65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...

    3.70

  • ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...

    5.87

  • 290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...

    3.76

Round 324
AccountPrize
65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...
5.87
290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...
3.76
ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...
$3.76
Round 325
Round winners
  • 65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...

    3.70

  • ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...

    5.87

  • 290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...

    3.76

Round Closed
AccountPrize
65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...
5.87
290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...
3.76
ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...
$3.76
Round Closed
AccountPrize
65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...
5.87
290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...
3.76
ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...
$3.76
Round Closed
AccountPrize
65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...
5.87
290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...
3.76
ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...
$3.76
Round Closed
AccountPrize
65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...
5.87
290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...
3.76
ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...
$3.76
Round Closed
AccountPrize
65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...
5.87
290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...
3.76
ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...
$3.76
Round Closed
AccountPrize
65bc6e9eab5c482e2e0035e21d4f638381677fa1 65bc6e9eab5c...
5.87
290f2e39ad4ed41f14a489d42cf9dbeb9a6940f3 290f2e39ad4e...
3.76
ff1f52e033734fea2b17bb99cb241095c852b19e ff1f52e03373...
$3.76
-------------------------------------------------------------------------------- /truffle/test/lotthereum.js: -------------------------------------------------------------------------------- 1 | var Lotthereum = artifacts.require("./Lotthereum.sol"); 2 | 3 | contract('Lotthereum', function(accounts) { 4 | it("should allow owner to create multiple games", function(done) { 5 | var lotthereum; 6 | Lotthereum.deployed().then(function(instance) { 7 | watcher = instance.RoundOpen({origin: accounts[0]}); 8 | 9 | instance.createGame(1, 20, 2, 20, {from: accounts[0]}).then(function() { 10 | return watcher.get(); 11 | }).then(function(events) { 12 | assert.equal(events.length, 1); 13 | assert.equal(events[0].args.gameId.valueOf(), 0); 14 | assert.equal(events[0].args.roundId.valueOf(), 0); 15 | 16 | instance.createGame(2, 10, 5, 50, {from: accounts[0]}).then(function() { 17 | return watcher.get(); 18 | }).then(function(events) { 19 | assert.equal(events.length, 1); 20 | assert.equal(events[0].args.gameId.valueOf(), 1); 21 | assert.equal(events[0].args.roundId.valueOf(), 0); 22 | 23 | instance.createGame(3, 10, 1, 10, {from: accounts[0]}).then(function() { 24 | return watcher.get(); 25 | }).then(function(events) { 26 | assert.equal(events.length, 1); 27 | assert.equal(events[0].args.gameId.valueOf(), 2); 28 | assert.equal(events[0].args.roundId.valueOf(), 0); 29 | done(); 30 | }); 31 | }); 32 | }); 33 | }); 34 | }); 35 | 36 | it("should allow owner to close games", function(done) { 37 | var lotthereum; 38 | Lotthereum.deployed().then(function(instance) { 39 | var watcher = instance.GameClosed({gameId: 2}); 40 | instance.closeGame(2, {from: accounts[0]}).then(function() { 41 | return watcher.get(); 42 | }).then(function(events) { 43 | assert.equal(events.length, 1); 44 | assert.equal(events[0].args.gameId, 2); 45 | 46 | instance.getGames().then(function(games) { 47 | assert.equal(games.length, 2); 48 | assert.equal(games[0].valueOf(), 0); 49 | assert.equal(games[1].valueOf(), 1); 50 | }).then(done).catch(done); 51 | }); 52 | }); 53 | }); 54 | 55 | it("should allow owner to open games", function(done) { 56 | var lotthereum; 57 | Lotthereum.deployed().then(function(instance) { 58 | var watcher = instance.GameOpened({gameId: 2}); 59 | instance.openGame(2, {from: accounts[0]}).then(function() { 60 | return watcher.get(); 61 | }).then(function(events) { 62 | assert.equal(events.length, 1); 63 | assert.equal(events[0].args.gameId, 2); 64 | 65 | instance.getGames().then(function(games) { 66 | assert.equal(games.length, 3); 67 | assert.equal(games[0].valueOf(), 0); 68 | assert.equal(games[1].valueOf(), 1); 69 | assert.equal(games[2].valueOf(), 2); 70 | }).then(done).catch(done); 71 | }); 72 | }); 73 | }); 74 | 75 | it("should allow clients to get the current games", function(done) { 76 | var lotthereum; 77 | Lotthereum.deployed().then(function(instance) { 78 | return instance.getGames().then(function(games) { 79 | assert.equal(games.length, 3); 80 | assert.equal(games[0].valueOf(), 0); 81 | assert.equal(games[1].valueOf(), 1); 82 | assert.equal(games[2].valueOf(), 2); 83 | }).then(done).catch(done); 84 | }); 85 | }); 86 | 87 | it("should allow clients to get a game current round id", function(done) { 88 | var lotthereum; 89 | Lotthereum.deployed().then(function(instance) { 90 | return instance.getGameCurrentRoundId(0).then(function(roundId) { 91 | assert.equal(roundId.valueOf(), 0); 92 | }).then(done).catch(done); 93 | }); 94 | }); 95 | 96 | it("should allow clients to know if the round is open", function(done) { 97 | var lotthereum; 98 | var gameId = 0; 99 | var roundId = 0; 100 | Lotthereum.deployed().then(function(instance) { 101 | return instance.getGameRoundOpen(gameId, roundId).then(function(roundOpen) { 102 | assert.isTrue(roundOpen); 103 | }).then(done).catch(done); 104 | }); 105 | }); 106 | 107 | it("should allow clients to get the minimum bet amount of a game", function(done) { 108 | var lotthereum; 109 | var gameId = 0; 110 | Lotthereum.deployed().then(function(instance) { 111 | return instance.getGameMinAmountByBet(gameId).then(function(minBet) { 112 | assert.equal(minBet, 2); 113 | }).then(done).catch(done); 114 | }); 115 | }); 116 | 117 | it("should allow clients to get the maximum number of bets of a game", function(done) { 118 | var lotthereum; 119 | var gameId = 0; 120 | Lotthereum.deployed().then(function(instance) { 121 | return instance.getGameMaxNumberOfBets(gameId).then(function(maxNumberOfBets) { 122 | assert.equal(maxNumberOfBets, 20); 123 | }).then(done).catch(done); 124 | }); 125 | }); 126 | 127 | it("should allow clients to get the prize of a game", function(done) { 128 | var lotthereum; 129 | var gameId = 0; 130 | Lotthereum.deployed().then(function(instance) { 131 | return instance.getGamePrize(gameId).then(function(prize) { 132 | assert.equal(prize, 20); 133 | }).then(done).catch(done); 134 | }); 135 | }); 136 | 137 | it("should allow clients to get the number of bets already placed in a game round", function(done) { 138 | var lotthereum; 139 | var gameId = 0; 140 | var roundId = 0; 141 | Lotthereum.deployed().then(function(instance) { 142 | return instance.getRoundNumberOfBets(gameId, roundId).then(function(numberOfBets) { 143 | assert.equal(numberOfBets, 0); 144 | }).then(done).catch(done); 145 | }); 146 | }); 147 | 148 | it("should emit a BetPlaced event for every bet placed", function(done) { 149 | var lotthereum; 150 | var account = accounts[1]; 151 | var gameId = 0; 152 | var roundId = 0; 153 | Lotthereum.deployed().then(function(instance) { 154 | var watcher = instance.BetPlaced({origin: account}); 155 | instance.placeBet(gameId, 0, {from: account, value: 2}).then(function() { 156 | return watcher.get(); 157 | }).then(function(events) { 158 | assert.equal(events.length, 1); 159 | assert.equal(events[0].args.origin, account); 160 | }).then(done).catch(done); 161 | }); 162 | }); 163 | 164 | it("should reject bets with value less than the game minAmountByBet", function(done) { 165 | var lotthereum; 166 | var account = accounts[1]; 167 | var gameId = 0; 168 | Lotthereum.deployed().then(function(instance) { 169 | instance.placeBet(gameId, 0, {from: account, value: 1}).then(function(result) { 170 | assert.isNotNull(result.tx); 171 | assert.equal(result.receipt.logs.length, 0); 172 | }).then(done).catch(done); 173 | }); 174 | }); 175 | 176 | it("should allow 18 bets to be placed", function(done) { 177 | var lotthereum; 178 | var account = accounts[1]; 179 | var gameId = 0; 180 | var roundId = 0; 181 | Lotthereum.deployed().then(function(instance) { 182 | 183 | watcher = instance.BetPlaced({origin: account}); 184 | for (var i = 1; i <= 9; i++) { 185 | instance.placeBet(gameId, i, {from: account, value: 2}).then(function() { 186 | return watcher.get(); 187 | }).then(function(events) { 188 | assert.equal(events.length, 1); 189 | assert.equal(events[0].args.origin, account); 190 | }) 191 | } 192 | 193 | account = accounts[2]; 194 | watcher = instance.BetPlaced({origin: account}); 195 | for (var i = 0; i <= 8; i++) { 196 | instance.placeBet(gameId, i, {from: account, value: 2}).then(function() { 197 | return watcher.get(); 198 | }).then(function(events) { 199 | assert.equal(events.length, 1); 200 | assert.equal(events[0].args.origin, account); 201 | }) 202 | } 203 | 204 | done(); 205 | }); 206 | }); 207 | 208 | it("should close the round when the last bet is placed", function(done) { 209 | var lotthereum; 210 | var account = accounts[2]; 211 | var gameId = 0; 212 | var roundId = 0; 213 | var next_round = 1; 214 | var prize = 20; 215 | var maxNumberOfBets = 20; 216 | var minAmountByBet = 2; 217 | 218 | Lotthereum.deployed().then(function(instance) { 219 | var betPlacedWatcher = instance.BetPlaced({origin: account}); 220 | var roundWinnerWatcher = instance.RoundWinner(); 221 | var roundCloseWatcher = instance.RoundClose({gameId: gameId, roundId: roundId}); 222 | var roundOpenWatcher = instance.RoundOpen({id: next_round}); 223 | instance.placeBet(gameId, 9, {from: account, value: 2}).then(function() { 224 | return betPlacedWatcher.get(); 225 | }).then(function(events) { 226 | assert.equal(events.length, 1); 227 | assert.equal(events[0].args.origin, account); 228 | return roundWinnerWatcher.get(); 229 | }).then(function(events) { 230 | assert.equal(events.length, 2); 231 | assert.isTrue(events[0].args.winnerAddress == accounts[1] || events[0].args.winnerAddress == accounts[2]) 232 | assert.equal(events[0].args.amount, prize / 2); 233 | return roundCloseWatcher.get(); 234 | }).then(function(events) { 235 | assert.equal(events.length, 1); 236 | assert.equal(events[0].args.gameId, gameId); 237 | assert.equal(events[0].args.roundId, roundId); 238 | return roundOpenWatcher.get(); 239 | }).then(function(events) { 240 | assert.equal(events.length, 1); 241 | assert.equal(events[0].args.gameId, gameId); 242 | assert.equal(events[0].args.roundId, next_round); 243 | }).then(done).catch(done); 244 | }); 245 | }); 246 | 247 | it("should allow clients to get the lucky number of a past round", function(done) { 248 | var lotthereum; 249 | var gameId = 0; 250 | var roundId = 0; 251 | Lotthereum.deployed().then(function(instance) { 252 | return instance.getRoundNumber(gameId, roundId).then(function(luckyNumber) { 253 | assert.isNotNull(luckyNumber); 254 | }).then(done).catch(done); 255 | }); 256 | }); 257 | 258 | it("should split the prize equaly by the round winners", function(done) { 259 | var lotthereum; 260 | var account = accounts[1]; 261 | Lotthereum.deployed().then(function(instance) { 262 | instance.getBalance({from: account}).then(function(balance) { 263 | assert.equal(balance.valueOf(), 10); 264 | }); 265 | account = accounts[2]; 266 | instance.getBalance({from: account}).then(function(balance) { 267 | assert.equal(balance.valueOf(), 10); 268 | }); 269 | }); 270 | done(); 271 | }); 272 | 273 | it("should run automatically without mannual intervention... lets place 50 bets", function(done) { 274 | var lotthereum; 275 | var account = accounts[0]; 276 | var gameId = 2; 277 | Lotthereum.deployed().then(function(instance) { 278 | var nextRoundId = 1; 279 | var numberOfRoundsOpenned = 0; 280 | 281 | var watcher = instance.BetPlaced({origin: account}); 282 | var roundOpenWatcher = instance.RoundOpen({id: nextRoundId}); 283 | 284 | var bet = -1; 285 | for (var i = 1; i <= 10; i++) { 286 | 287 | bet++; 288 | if (bet > 9) { 289 | bet = 0; 290 | } 291 | instance.placeBet(gameId, bet, {from: account, value: 5}).then(function() { 292 | return watcher.get(); 293 | }).then(function(events) { 294 | if (typeof events != 'undefined') { 295 | if (events.length > 0) { 296 | assert.equal(events[0].args.origin, account); 297 | return roundOpenWatcher.get(); 298 | } 299 | } 300 | }).then(function(events) { 301 | if (typeof events != 'undefined') { 302 | if (events.length > 0) { 303 | assert.equal(events.length, 1); 304 | assert.equal(events[0].args.gameId.valueOf(), gameId); 305 | assert.equal(events[0].args.roundId.valueOf(), 1); 306 | numberOfRoundsOpenned++; 307 | nextRoundId++; 308 | return numberOfRoundsOpenned; 309 | } 310 | } 311 | }).then(function(numberOfRoundsOpenned) { 312 | if (numberOfRoundsOpenned == 10) { 313 | done(); 314 | } 315 | }) 316 | } 317 | }); 318 | }); 319 | 320 | }); 321 | -------------------------------------------------------------------------------- /truffle/contracts/Lotthereum.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.16; 2 | 3 | 4 | contract SafeMath { 5 | 6 | // ensure that the result of adding x and y is accurate 7 | function add(uint x, uint y) internal constant returns (uint z) { 8 | assert((z = x + y) >= x); 9 | } 10 | 11 | // ensure that the result of subtracting y from x is accurate 12 | function subtract(uint x, uint y) internal constant returns (uint z) { 13 | assert((z = x - y) <= x); 14 | } 15 | 16 | // ensure that the result of multiplying x and y is accurate 17 | function multiply(uint x, uint y) internal constant returns (uint z) { 18 | z = x * y; 19 | assert(x == 0 || z / x == y); 20 | return z; 21 | } 22 | 23 | // ensure that the result of dividing x and y is accurate 24 | // note: Solidity now throws on division by zero, so a check is not needed 25 | function divide(uint x, uint y) internal constant returns (uint z) { 26 | z = x / y; 27 | assert(x == ( (y * z) + (x % y) )); 28 | return z; 29 | } 30 | 31 | // return the lowest of two 64 bit integers 32 | function min64(uint64 x, uint64 y) internal constant returns (uint64) { 33 | return x < y ? x: y; 34 | } 35 | 36 | // return the largest of two 64 bit integers 37 | function max64(uint64 x, uint64 y) internal constant returns (uint64) { 38 | return x >= y ? x : y; 39 | } 40 | 41 | // return the lowest of two values 42 | function min(uint x, uint y) internal constant returns (uint) { 43 | return (x <= y) ? x : y; 44 | } 45 | 46 | // return the largest of two values 47 | function max(uint x, uint y) internal constant returns (uint) { 48 | return (x >= y) ? x : y; 49 | } 50 | 51 | function assert(bool assertion) internal { 52 | if (!assertion) { 53 | revert(); 54 | } 55 | } 56 | } 57 | 58 | 59 | contract Owned { 60 | address owner; 61 | 62 | modifier onlyowner() { 63 | if (msg.sender == owner) { 64 | _; 65 | } 66 | } 67 | 68 | function Owned() internal { 69 | owner = msg.sender; 70 | } 71 | } 72 | 73 | 74 | contract Mortal is Owned { 75 | function kill() public onlyowner { 76 | selfdestruct(owner); 77 | } 78 | } 79 | 80 | 81 | contract Lotthereum is Mortal, SafeMath { 82 | 83 | Game[] private games; 84 | mapping (address => uint) private balances; // balances per address 85 | 86 | struct Game { 87 | uint id; 88 | bool open; 89 | uint pointer; 90 | uint maxNumberOfBets; 91 | uint minAmountByBet; 92 | uint prize; 93 | uint currentRound; 94 | Round[] rounds; 95 | } 96 | 97 | struct Round { 98 | uint id; 99 | uint pointer; 100 | bytes32 hash; 101 | bool open; 102 | uint8 number; 103 | Bet[] bets; 104 | address[] winners; 105 | } 106 | 107 | struct Bet { 108 | uint id; 109 | address origin; 110 | uint amount; 111 | uint8 bet; 112 | uint round; 113 | } 114 | 115 | event RoundOpen( 116 | uint indexed gameId, 117 | uint indexed roundId 118 | ); 119 | event RoundClose( 120 | uint indexed gameId, 121 | uint indexed roundId, 122 | uint8 number 123 | ); 124 | event MaxNumberOfBetsChanged( 125 | uint maxNumberOfBets 126 | ); 127 | event MinAmountByBetChanged( 128 | uint minAmountByBet 129 | ); 130 | event BetPlaced( 131 | uint indexed gameId, 132 | uint indexed roundId, 133 | address indexed origin, 134 | uint betId 135 | ); 136 | event RoundWinner( 137 | uint indexed gameId, 138 | uint indexed roundId, 139 | address indexed winnerAddress, 140 | uint amount 141 | ); 142 | event GameOpened( 143 | uint indexed gameId 144 | ); 145 | event GameClosed( 146 | uint indexed gameId 147 | ); 148 | 149 | function createGame( 150 | uint pointer, 151 | uint maxNumberOfBets, 152 | uint minAmountByBet, 153 | uint prize 154 | ) onlyowner returns (uint id) { 155 | id = games.length; 156 | games.length += 1; 157 | games[id].id = id; 158 | games[id].pointer = pointer; 159 | games[id].maxNumberOfBets = maxNumberOfBets; 160 | games[id].minAmountByBet = minAmountByBet; 161 | games[id].prize = prize; 162 | games[id].open = true; 163 | games[id].currentRound = createGameRound(id); 164 | } 165 | 166 | function closeGame(uint gameId) onlyowner returns (bool) { 167 | games[gameId].open = false; 168 | GameClosed(gameId); 169 | return true; 170 | } 171 | 172 | function openGame(uint gameId) onlyowner returns (bool) { 173 | games[gameId].open = true; 174 | GameOpened(gameId); 175 | return true; 176 | } 177 | 178 | function createGameRound(uint gameId) internal returns (uint id) { 179 | id = games[gameId].rounds.length; 180 | games[gameId].rounds.length += 1; 181 | games[gameId].rounds[id].id = id; 182 | games[gameId].rounds[id].open = true; 183 | RoundOpen(gameId, id); 184 | } 185 | 186 | function payout(uint gameId) internal { 187 | address[] winners = games[gameId].rounds[games[gameId].currentRound].winners; 188 | for (uint i = 0; i < games[gameId].maxNumberOfBets -1; i++) { 189 | if (games[gameId].rounds[games[gameId].currentRound].bets[i].bet == games[gameId].rounds[games[gameId].currentRound].number) { 190 | uint id = winners.length; 191 | winners.length += 1; 192 | winners[id] = games[gameId].rounds[games[gameId].currentRound].bets[i].origin; 193 | } 194 | } 195 | 196 | if (winners.length > 0) { 197 | uint prize = divide(games[gameId].prize, winners.length); 198 | for (i = 0; i < winners.length; i++) { 199 | balances[winners[i]] = add(balances[winners[i]], prize); 200 | RoundWinner(gameId, games[gameId].currentRound, winners[i], prize); 201 | } 202 | } 203 | } 204 | 205 | function closeRound(uint gameId) internal { 206 | games[gameId].rounds[games[gameId].currentRound].open = false; 207 | games[gameId].rounds[games[gameId].currentRound].hash = getBlockHash(games[gameId].pointer); 208 | games[gameId].rounds[games[gameId].currentRound].number = getNumber(games[gameId].rounds[games[gameId].currentRound].hash); 209 | // games[gameId].pointer = games[gameId].rounds[games[gameId].currentRound].number; 210 | payout(gameId); 211 | RoundClose( 212 | gameId, 213 | games[gameId].rounds[games[gameId].currentRound].id, 214 | games[gameId].rounds[games[gameId].currentRound].number 215 | ); 216 | games[gameId].currentRound = createGameRound(gameId); 217 | } 218 | 219 | function getBlockHash(uint i) constant returns (bytes32 blockHash) { 220 | if (i > 255) { 221 | i = 255; 222 | } 223 | blockHash = block.blockhash(block.number - i); 224 | } 225 | 226 | function getNumber(bytes32 _a) constant returns (uint8) { 227 | uint8 _b = 1; 228 | uint8 mint = 0; 229 | bool decimals = false; 230 | for (uint i = _a.length - 1; i >= 0; i--) { 231 | if ((_a[i] >= 48) && (_a[i] <= 57)) { 232 | if (decimals) { 233 | if (_b == 0) { 234 | break; 235 | } else { 236 | _b--; 237 | } 238 | } 239 | mint *= 10; 240 | mint += uint8(_a[i]) - 48; 241 | return mint; 242 | } else if (_a[i] == 46) { 243 | decimals = true; 244 | } 245 | } 246 | return mint; 247 | } 248 | 249 | function placeBet(uint gameId, uint8 bet) public payable returns (bool) { 250 | if (!games[gameId].rounds[games[gameId].currentRound].open) { 251 | return false; 252 | } 253 | 254 | if (msg.value < games[gameId].minAmountByBet) { 255 | return false; 256 | } 257 | 258 | if (games[gameId].rounds[games[gameId].currentRound].bets.length < games[gameId].maxNumberOfBets) { 259 | uint id = games[gameId].rounds[games[gameId].currentRound].bets.length; 260 | games[gameId].rounds[games[gameId].currentRound].bets.length += 1; 261 | games[gameId].rounds[games[gameId].currentRound].bets[id].id = id; 262 | games[gameId].rounds[games[gameId].currentRound].bets[id].round = games[gameId].rounds[games[gameId].currentRound].id; 263 | games[gameId].rounds[games[gameId].currentRound].bets[id].bet = bet; 264 | games[gameId].rounds[games[gameId].currentRound].bets[id].origin = msg.sender; 265 | games[gameId].rounds[games[gameId].currentRound].bets[id].amount = msg.value; 266 | BetPlaced(gameId, games[gameId].rounds[games[gameId].currentRound].id, msg.sender, id); 267 | } 268 | 269 | if (games[gameId].rounds[games[gameId].currentRound].bets.length >= games[gameId].maxNumberOfBets) { 270 | closeRound(gameId); 271 | } 272 | 273 | return true; 274 | } 275 | 276 | function withdraw() public returns (uint) { 277 | uint amount = getBalance(); 278 | if (amount > 0) { 279 | balances[msg.sender] = 0; 280 | msg.sender.transfer(amount); 281 | return amount; 282 | } 283 | return 0; 284 | } 285 | 286 | function getBalance() constant returns (uint) { 287 | if ((balances[msg.sender] > 0) && (balances[msg.sender] < this.balance)) { 288 | return balances[msg.sender]; 289 | } 290 | return 0; 291 | } 292 | 293 | function numberOfClosedGames() constant returns(uint numberOfClosedGames) { 294 | numberOfClosedGames = 0; 295 | for (uint i = 0; i < games.length; i++) { 296 | if (games[i].open != true) { 297 | numberOfClosedGames++; 298 | } 299 | } 300 | return numberOfClosedGames; 301 | } 302 | 303 | function getGames() constant returns(uint[] memory ids) { 304 | ids = new uint[](games.length - numberOfClosedGames()); 305 | for (uint i = 0; i < games.length; i++) { 306 | if (games[i].open == true) { 307 | ids[i] = games[i].id; 308 | } 309 | } 310 | } 311 | 312 | function getGameCurrentRoundId(uint gameId) constant returns(uint) { 313 | return games[gameId].currentRound; 314 | } 315 | 316 | function getGameRoundOpen(uint gameId, uint roundId) constant returns(bool) { 317 | return games[gameId].rounds[roundId].open; 318 | } 319 | 320 | function getGameMaxNumberOfBets(uint gameId) constant returns(uint) { 321 | return games[gameId].maxNumberOfBets; 322 | } 323 | 324 | function getGameMinAmountByBet(uint gameId) constant returns(uint) { 325 | return games[gameId].minAmountByBet; 326 | } 327 | 328 | function getGamePrize(uint gameId) constant returns(uint) { 329 | return games[gameId].prize; 330 | } 331 | 332 | function getRoundNumberOfBets(uint gameId, uint roundId) constant returns(uint) { 333 | return games[gameId].rounds[roundId].bets.length; 334 | } 335 | 336 | function getRoundBetOrigin(uint gameId, uint roundId, uint betId) constant returns(address) { 337 | return games[gameId].rounds[roundId].bets[betId].origin; 338 | } 339 | 340 | function getRoundBetAmount(uint gameId, uint roundId, uint betId) constant returns(uint) { 341 | return games[gameId].rounds[roundId].bets[betId].amount; 342 | } 343 | 344 | function getRoundBetNumber(uint gameId, uint roundId, uint betId) constant returns(uint) { 345 | return games[gameId].rounds[roundId].bets[betId].bet; 346 | } 347 | 348 | function getRoundNumber(uint gameId, uint roundId) constant returns(uint8) { 349 | return games[gameId].rounds[roundId].number; 350 | } 351 | 352 | function getRoundPointer(uint gameId, uint roundId) constant returns(uint) { 353 | return games[gameId].rounds[roundId].pointer; 354 | } 355 | 356 | function getPointer(uint gameId) constant returns(uint) { 357 | return games[gameId].pointer; 358 | } 359 | 360 | function () payable { 361 | } 362 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Lotthereum 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 54 | 55 |
56 |
57 |
58 |
59 |
60 | 61 | 62 |
63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 |
71 |
72 | 73 | 74 |
75 |
76 | {betButton} 77 | 78 |
79 | 80 | lock Round {round_id} 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
{prize} ether prize{minAmount} ether per bet{numberOfBets} bets placed{remaining} bets remaining
92 | 93 |
94 |
95 |
96 | 97 | 98 |
    {bets}
99 | 100 |
101 |
102 |
103 | 104 | 105 | 113 | 114 | 115 | 118 | 119 | 120 | 123 | 124 |
125 | 126 |
127 | 128 |
129 |
130 |
131 |
132 |
133 |
    134 |
  • help What is Lotthereum?
  • 135 |
  • stars Lotthereum is a decentralized open source Ethereum based lottery
  • 136 |
  • stars Every Lotthereum round a random number is fetched from the hash of one of the latest 256 blocks from Ethereum blockchain
  • 137 |
  • stars Lotthereum has NO house edge, NO signup, no deposits
  • 138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
    148 |
  • question_answer How does Lotthereum works?
  • 149 |
  • looks_one Players have to guess a number between 0-9
  • 150 |
  • looks_two Every round has a fixed prize, a fixed value per bet, and a fixed number of participants
  • 151 |
  • looks_3 The contract will reveal the lucky number right after the last bet is placed
  • 152 |
  • looks_4 The prize is divided equally by all winners
  • 153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
    163 |
  • done_all Lotthereum vs Etheroll
  • 164 |
  • done Lotthereum has NO fees
  • 165 |
  • done Lotthereum does NOT use oracles or external contract transactions
  • 166 |
  • done Lotthereum does NOT keep records or check IP addresses
  • 167 | 168 |
169 |
170 |
171 |
172 |
173 |
174 | 177 |
178 |
179 | 180 |
181 |
182 |
183 |
184 |
185 | monetization_on Withdraw 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
Account:
Balance:
198 | 199 |
200 | 201 |
202 | 203 | attach_money WITHDRAW 204 |
205 |
206 |
207 |
208 |
209 |
210 | 211 | 212 | 240 | 241 | 242 | 261 | 262 | 263 | 342 | 343 | 366 | 367 | 368 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 449 | 450 | 451 | -------------------------------------------------------------------------------- /app/scripts/lotthereum.js: -------------------------------------------------------------------------------- 1 | function Lotthereum() { 2 | this.contract = web3.eth.contract([{'constant':false,'inputs':[{'name':'gameId','type':'uint256'}],'name':'openGame','outputs':[{'name':'','type':'bool'}],'payable':false,'type':'function'},{'constant':false,'inputs':[{'name':'gameId','type':'uint256'},{'name':'bet','type':'uint8'}],'name':'placeBet','outputs':[{'name':'','type':'bool'}],'payable':true,'type':'function'},{'constant':true,'inputs':[],'name':'getBalance','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':false,'inputs':[{'name':'gameId','type':'uint256'}],'name':'closeGame','outputs':[{'name':'','type':'bool'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'_a','type':'bytes32'}],'name':'getNumber','outputs':[{'name':'','type':'uint8'}],'payable':false,'type':'function'},{'constant':false,'inputs':[],'name':'withdraw','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':false,'inputs':[],'name':'kill','outputs':[],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'},{'name':'roundId','type':'uint256'}],'name':'getGameRoundOpen','outputs':[{'name':'','type':'bool'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'},{'name':'roundId','type':'uint256'}],'name':'getRoundPointer','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[],'name':'numberOfClosedGames','outputs':[{'name':'numberOfClosedGames','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'}],'name':'getGameMinAmountByBet','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'},{'name':'roundId','type':'uint256'}],'name':'getRoundNumberOfBets','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'},{'name':'roundId','type':'uint256'},{'name':'betId','type':'uint256'}],'name':'getRoundBetOrigin','outputs':[{'name':'','type':'address'}],'payable':false,'type':'function'},{'constant':false,'inputs':[{'name':'pointer','type':'uint256'},{'name':'maxNumberOfBets','type':'uint256'},{'name':'minAmountByBet','type':'uint256'},{'name':'prize','type':'uint256'}],'name':'createGame','outputs':[{'name':'id','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[],'name':'getGames','outputs':[{'name':'ids','type':'uint256[]'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'},{'name':'roundId','type':'uint256'}],'name':'getRoundNumber','outputs':[{'name':'','type':'uint8'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'}],'name':'getGameCurrentRoundId','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'},{'name':'roundId','type':'uint256'},{'name':'betId','type':'uint256'}],'name':'getRoundBetNumber','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'}],'name':'getGameMaxNumberOfBets','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'}],'name':'getPointer','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'}],'name':'getGamePrize','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'i','type':'uint256'}],'name':'getBlockHash','outputs':[{'name':'blockHash','type':'bytes32'}],'payable':false,'type':'function'},{'constant':true,'inputs':[{'name':'gameId','type':'uint256'},{'name':'roundId','type':'uint256'},{'name':'betId','type':'uint256'}],'name':'getRoundBetAmount','outputs':[{'name':'','type':'uint256'}],'payable':false,'type':'function'},{'payable':true,'type':'fallback'},{'anonymous':false,'inputs':[{'indexed':true,'name':'gameId','type':'uint256'},{'indexed':true,'name':'roundId','type':'uint256'}],'name':'RoundOpen','type':'event'},{'anonymous':false,'inputs':[{'indexed':true,'name':'gameId','type':'uint256'},{'indexed':true,'name':'roundId','type':'uint256'},{'indexed':false,'name':'number','type':'uint8'}],'name':'RoundClose','type':'event'},{'anonymous':false,'inputs':[{'indexed':false,'name':'maxNumberOfBets','type':'uint256'}],'name':'MaxNumberOfBetsChanged','type':'event'},{'anonymous':false,'inputs':[{'indexed':false,'name':'minAmountByBet','type':'uint256'}],'name':'MinAmountByBetChanged','type':'event'},{'anonymous':false,'inputs':[{'indexed':true,'name':'gameId','type':'uint256'},{'indexed':true,'name':'roundId','type':'uint256'},{'indexed':true,'name':'origin','type':'address'},{'indexed':false,'name':'betId','type':'uint256'}],'name':'BetPlaced','type':'event'},{'anonymous':false,'inputs':[{'indexed':true,'name':'gameId','type':'uint256'},{'indexed':true,'name':'roundId','type':'uint256'},{'indexed':true,'name':'winnerAddress','type':'address'},{'indexed':false,'name':'amount','type':'uint256'}],'name':'RoundWinner','type':'event'},{'anonymous':false,'inputs':[{'indexed':true,'name':'gameId','type':'uint256'}],'name':'GameOpened','type':'event'},{'anonymous':false,'inputs':[{'indexed':true,'name':'gameId','type':'uint256'}],'name':'GameClosed','type':'event'}]); 3 | 4 | this.contractInstance = this.contract.at(window.contract_address); 5 | this.modalIntro = 0; 6 | this.tabbed = 0; 7 | this.selectedGame = 0; 8 | this.eventsInitialized = false; 9 | this.games = []; 10 | var self = this; 11 | 12 | /////////////////////////////////////////////////////////////////////////// 13 | // 14 | // INIT 15 | // 16 | this.init = function () { 17 | async.waterfall([ 18 | function games(done) { 19 | self.contractInstance.getGames(function(error, result) { 20 | console.log('result.valueOf(): ' + result.valueOf()) 21 | var games = result.valueOf(); 22 | for (var i = 0; i < games.length; i++) { 23 | self.games.push(new Game(self, i)); 24 | self.addGamePlaceHolder(i); 25 | } 26 | done(error, self.games); 27 | }); 28 | }, 29 | ], 30 | function (err) { 31 | if (err) { 32 | console.error(err); 33 | } 34 | }); 35 | }; 36 | 37 | this.isInitialized = function (gameId, roundId) { 38 | var initialized = false; 39 | if (self.games[gameId]){ 40 | if (self.games[gameId].rounds[roundId]) { 41 | self.games[gameId].rounds[roundId].rendered++; 42 | console.log('rendered: ' + self.games[gameId].rounds[roundId].rendered) 43 | console.log('numberOfBets: ' + self.games[gameId].rounds[roundId].numberOfBets) 44 | if (self.games[gameId].rounds[roundId].rendered >= self.games[gameId].rounds[roundId].numberOfBets) { 45 | self.games[gameId].rounds[roundId].initialized = true; 46 | initialized = true; 47 | } 48 | } 49 | } 50 | 51 | if (initialized) { 52 | console.log('GAME ' + gameId + ' INITIALIZED!'); 53 | self.renderRound(gameId, roundId); 54 | 55 | if (!self.eventsInitialized) { 56 | self.eventsInitialized = true; 57 | this.initEvents(); 58 | 59 | if(self.games[gameId].rounds[roundId].payload) { 60 | if(self.games[gameId].rounds[roundId].payload.select) { 61 | console.log('>>>>>>>>>>>>>>>>>>>>>>>>', self.payload); 62 | $('ul.tabs').tabs('select_tab', '#game_' + self.gameId + '_holder'); 63 | } 64 | } 65 | } 66 | } 67 | return initialized; 68 | } 69 | 70 | this.areAllRoundsInitialed = function () { 71 | var ret = true; 72 | for (var i = 0; i <= self.currentRoundId; i++) { 73 | if (self.rounds[i].initialized == false) { 74 | ret = false; 75 | } 76 | } 77 | return ret 78 | } 79 | // 80 | // INIT 81 | // 82 | /////////////////////////////////////////////////////////////////////////// 83 | 84 | /////////////////////////////////////////////////////////////////////////// 85 | // 86 | // PLACE BET 87 | // 88 | this.placeBet = function (gameId, bet) { 89 | var value = self.games[gameId].minAmount * 1000000000000000000 90 | var gas = 500000; 91 | console.log('sending from: ' + self.account) 92 | 93 | self.contractInstance.placeBet(gameId, bet, {from: self.account, value: value, gas: gas}, function(error, result) { 94 | if (error) { 95 | console.log('ERROR:'); 96 | console.log(error); 97 | } else { 98 | $('#bet-btn').addClass('disabled'); 99 | Materialize.fadeInImage('#confirmations'); 100 | console.log(result); 101 | } 102 | }); 103 | } 104 | // 105 | // PLACE BET 106 | // 107 | /////////////////////////////////////////////////////////////////////////// 108 | 109 | /////////////////////////////////////////////////////////////////////////// 110 | // 111 | // WITHDRAW 112 | // 113 | this.withdraw = function () { 114 | var gas = 500000; 115 | console.log('withdraw from: ' + self.account) 116 | self.contractInstance.withdraw({from: self.account, gas: gas}, function(error, result) { 117 | $('#withdraw-btn').addClass('disabled'); 118 | $('#withdraw-transaction-id').html(result) 119 | Materialize.fadeInImage('#withdraw-confirmations'); 120 | }); 121 | } 122 | // 123 | // WITHDRAW 124 | // 125 | /////////////////////////////////////////////////////////////////////////// 126 | 127 | /////////////////////////////////////////////////////////////////////////// 128 | // 129 | // ACCOUNTS 130 | // 131 | this.initAccounts = function () { 132 | self.accounts = []; 133 | self.account = null; 134 | 135 | web3.eth.getAccounts(function(error, accounts) { 136 | var account = null; 137 | accounts.forEach(function(_account) { 138 | if (account == null) { 139 | account = _account; 140 | } 141 | $('#dropdown-nav').append('
  • ' + _account + '
    ' + _account + '
  • '); 142 | }); 143 | 144 | if (account != null) { 145 | self.changeAccount(account) 146 | $('#avatar').css('display', 'block'); 147 | } else { 148 | console.log('No accounts found!'); 149 | $('#alert1').modal('open'); 150 | } 151 | 152 | $('select').material_select(); 153 | 154 | $('.accounts_dropdown_item').click(function() { 155 | var newValue = $(this).attr('href').replace('#!', ''); 156 | console.log(newValue); 157 | self.changeAccount(newValue) 158 | }); 159 | }); 160 | } 161 | 162 | this.changeAccount = function (address) { 163 | self.account = address; 164 | self.renderAvatar(self.account); 165 | $('#current_account_number').html(self.account); 166 | self.getBalance(); 167 | } 168 | 169 | this.getBalance = function () { 170 | self.contractInstance.getBalance({from: self.account}, function(error, result){ 171 | var balance = web3.fromWei(result.valueOf(), 'ether'); 172 | $('#current_account_balance').html(balance + ' ETH'); 173 | if (balance <= 0) { 174 | $('#withdraw-btn').addClass('disabled'); 175 | } else { 176 | $('#withdraw-btn').removeClass('disabled'); 177 | } 178 | }); 179 | } 180 | // 181 | // ACCOUNTS 182 | // 183 | /////////////////////////////////////////////////////////////////////////// 184 | 185 | /////////////////////////////////////////////////////////////////////////// 186 | // 187 | // EVENTS 188 | // 189 | this.initEvents = function () { 190 | var currentBlockNumber = 0; 191 | web3.eth.getBlockNumber(function(error, result){ 192 | if(!error) 193 | currentBlockNumber = result; 194 | else 195 | console.error(error); 196 | }) 197 | 198 | var range = {fromBlock: currentBlockNumber, toBlock: 'latest'}; 199 | self.betPlacedEvent = self.contractInstance.BetPlaced(range); 200 | self.betPlacedEvent.watch(self.betPlaced); 201 | 202 | self.roundCloseEvent = self.contractInstance.RoundClose(range); 203 | self.roundCloseEvent.watch(self.roundClose); 204 | 205 | self.roundOpenEvent = self.contractInstance.RoundOpen(range); 206 | self.roundOpenEvent.watch(self.roundOpen); 207 | 208 | self.roundWinnerEvent = self.contractInstance.RoundWinner(range); 209 | self.roundWinnerEvent.watch(self.roundWinner); 210 | } 211 | 212 | this.roundClose = function (error, event) { 213 | console.log('round closed: ' + event.args.roundId + ' (game: ' + event.args.gameId); 214 | Materialize.toast('Game #' + event.args.gameId + ' Round #' + event.args.roundId + ' closed!', 5000, 'rounded'); 215 | } 216 | 217 | this.roundOpen = function (error, event) { 218 | console.log('round opened: ' + event.args.roundId + ' (game: ' + event.args.gameId); 219 | Materialize.toast('Game #' + event.args.gameId + ' Round #' + event.args.roundId + ' opened!', 5000, 'rounded'); 220 | self.games[event.args.gameId].init(); 221 | } 222 | 223 | this.betPlaced = function (error, event) { 224 | if (error) { 225 | console.log('ERROR:'); 226 | console.log(error); 227 | } else { 228 | console.log('betPlaced event origin: ' + event.args.origin); 229 | console.log('event.args.gameId: ' + event.args.gameId); 230 | console.log('event.args.roundId: ' + event.args.roundId); 231 | if (event.args.origin == self.account) { 232 | $('#bet-modal').modal('close'); 233 | $('#bet-btn').removeClass('disabled'); 234 | $('#confirmations').css('opacity', 0); 235 | } 236 | Materialize.toast('New bet placed', 5000, 'rounded'); 237 | self.games[event.args.gameId].rounds[event.args.roundId].init({'select': true}); 238 | } 239 | } 240 | 241 | this.roundWinner = function (error, event) { 242 | console.log('round winner: ' + event.args.winnerAddress + ' ' + web3.fromWei(event.args.amount, 'ether') + ' ETH'); 243 | Materialize.toast('Round winner ' + event.args.winnerAddress, 5000, 'rounded'); 244 | self.getBalance(); 245 | } 246 | // 247 | // EVENTS 248 | // 249 | /////////////////////////////////////////////////////////////////////////// 250 | 251 | /////////////////////////////////////////////////////////////////////////// 252 | // 253 | // RENDER 254 | // 255 | this.renderRound = function (gameId, roundId) { 256 | var game = self.games[gameId]; 257 | var round = self.games[gameId].rounds[roundId]; 258 | console.log(round) 259 | 260 | console.log('redering game #' + gameId + ' round #' + roundId); 261 | $('#rounds_' + gameId + ' #round_' + roundId + '_holder').html(''); 262 | 263 | var html = $('#round_template').html(); 264 | html = html.replace(/{round_id}/g, roundId); 265 | if (round.open) { 266 | html = html.replace('>lock<', '>lock_open<'); 267 | html = html.replace('{color}', 'blue'); 268 | var bet_btn_html = $('#bet_button_template').html().replace('{game_id}', gameId); 269 | html = html.replace('{betButton}', bet_btn_html); 270 | } else { 271 | html = html.replace('{color}', 'grey lighten-1'); 272 | } 273 | html = html.replace(/{prize}/g, game.prize); 274 | html = html.replace('{minAmount}', game.minAmount); 275 | html = html.replace('{minAmount}', game.minAmount); 276 | html = html.replace('{numberOfBets}', round.numberOfBets); 277 | html = html.replace('{remaining}', round.remaining); 278 | html = html.replace(/{progress}/g, round.progress); 279 | 280 | // render bets 281 | var bets_html = '' 282 | var has_winner = false; 283 | for (var j = round.bets.length-1; j >= 0; j--) { 284 | var bet_html = $('#bet_template').html(); 285 | 286 | if ((!round.open) && (round.bets[j].bet == round.number)) { 287 | bet_html = bet_html.replace(' {win}', ' green lighten-3'); 288 | has_winner = true; 289 | } else { 290 | bet_html = bet_html.replace(' {win}', ''); 291 | } 292 | 293 | bet_html = bet_html.replace(/{bet_id}/g, round.bets[j].id); 294 | bet_html = bet_html.replace(/{origin}/g, round.bets[j].origin); 295 | bet_html = bet_html.replace(/{amount}/g, round.bets[j].amount); 296 | bets_html += bet_html; 297 | } 298 | html = html.replace('{bets}', bets_html); 299 | 300 | if (!round.open) { 301 | var btnHtml = $('#bet_number_template').html().replace(/{number}/g, round.number); 302 | if (has_winner) { 303 | btnHtml = btnHtml.replace('{color}', 'green'); 304 | html = html.replace('{betButton}', btnHtml); 305 | } else { 306 | btnHtml = btnHtml.replace('{color}', 'red'); 307 | html = html.replace('{betButton}', btnHtml); 308 | } 309 | } 310 | 311 | $('#rounds_' + gameId + ' #round_' + roundId + '_holder').html(html); 312 | $('#rounds_' + gameId + ' #round_' + roundId + '_progress').css('width', round.progress + '%'); 313 | 314 | if (self.areAllRoundsInitialed()) { 315 | console.log('INITIALIZED!!!!'); 316 | this.renderTabs(); 317 | self.stopLoading(); 318 | } 319 | } 320 | 321 | this.addGamePlaceHolder = function (gameId) { 322 | var html = $('#game_placeholder_template').html(); 323 | html = html.replace(/{game_id}/g, gameId); 324 | $('#games-container').append(html); 325 | } 326 | 327 | this.addRoundPlaceHolder = function (gameId, roundId) { 328 | var html = $('#round_placeholder_template').html(); 329 | html = html.replace(/{round_id}/g, roundId); 330 | $('#rounds_' + gameId).prepend(html); 331 | } 332 | 333 | this.stopLoading = function () { 334 | self.renderAllIdenticons(); 335 | $('#loading').hide(); 336 | $('.page-footer').css('display', 'block'); 337 | $('.main-container').css('display', 'block'); 338 | $('.navbar-fixed').css('display', 'block'); 339 | $('.nav-wrapper').css('background', '#2196F3'); 340 | } 341 | 342 | this.startLoading = function () { 343 | $('#loading').show(); 344 | $('.page-footer').css('display', 'none'); 345 | $('.main-container').css('display', 'none'); 346 | $('.navbar-fixed').css('display', 'none'); 347 | $('.nav-wrapper').css('background', 'white'); 348 | } 349 | 350 | this.renderIdenticon = function (obj) { 351 | obj.style.backgroundImage = 'url(' + blockies.create({ seed:obj.innerHTML.toLowerCase(), size: 8, scale: 16}).toDataURL() + ')' 352 | } 353 | 354 | this.renderAllIdenticons = function () { 355 | $('.eth-address').each(function(i, obj) { 356 | self.renderIdenticon(obj) 357 | }); 358 | } 359 | 360 | this.initUIElements = function () { 361 | $(document).ready(function() { 362 | $('.target').pushpin({top: 0, bottom: 1000, offset: 0}); 363 | 364 | $('#bet-btn').click(function() { 365 | var bet = parseInt($('input[name=bet_pick]:checked').val()); 366 | if (bet <= 9 && bet >= 0) { 367 | self.placeBet(self.selectedGame, $('input[name=bet_pick]:checked').val()); 368 | } 369 | }); 370 | 371 | $('#withdraw-btn').click(function() { 372 | self.withdraw(); 373 | }); 374 | 375 | $('#close-intro-btn').click(function() { 376 | $('#intro').css('display', 'none'); 377 | }); 378 | 379 | $('#close-intro-btn').click(function() { 380 | $('#intro').css('display', 'none'); 381 | }); 382 | 383 | $('#logo-btn').click(function() { 384 | $('ul.tabs').tabs('select_tab', 'intro1'); 385 | }); 386 | 387 | $('#place-your-bet').click(function() { 388 | window.scrollTo(0, 0); 389 | $('ul.tabs').tabs('select_tab', 'game_3_holder'); 390 | }); 391 | 392 | $(window).scroll(function() { 393 | var opacity = 1 - $(window).scrollTop() / 70; 394 | if (opacity < 0) { 395 | opacity = 0; 396 | } 397 | $('.tabs').css('opacity', opacity); 398 | }); 399 | 400 | $('.modal').modal(); 401 | 402 | $('.tooltipped').tooltip({delay: 60}); 403 | 404 | $('.main-container').css('display', 'block'); 405 | }); 406 | } 407 | 408 | this.renderAvatar = function (address) { 409 | $('#avatar').html(address); 410 | $('#avatar').each(function(i, obj) { 411 | self.renderIdenticon(obj) 412 | }); 413 | } 414 | 415 | this.renderTabs = function () { 416 | self.tabbed++; 417 | if (self.tabbed <= 1) { 418 | $('#tabs').html('
  • Lotthereum
  • '); 419 | for (var i = this.games.length-1; i >= 0; i--) { 420 | $('#tabs').append('
  • ' + this.games[i].minAmount +' ETH
  • '); 421 | } 422 | $('#tabs').append('
  • Withdraw
  • '); 423 | $('#tabs').tabs({ 424 | 'swipeable': false, 425 | 'onShow': function (tab) { 426 | self.selectedGame = 0; 427 | var gameId = parseInt( 428 | tab.selector.replace('#game_', '').replace('_holder', '') 429 | ); 430 | if (gameId >= 0) { 431 | self.selectedGame = gameId; 432 | } 433 | console.log('SELECTED GAME: ' + self.selectedGame); 434 | }, 435 | }); 436 | } 437 | } 438 | // 439 | // RENDER 440 | // 441 | /////////////////////////////////////////////////////////////////////////// 442 | 443 | this.init(); 444 | this.initAccounts(); 445 | this.initUIElements(); 446 | } 447 | 448 | function Game(lotthereum, gameId) { 449 | this.id = gameId; 450 | this.lotthereum = lotthereum; 451 | this.contractInstance = lotthereum.contractInstance; 452 | 453 | this.rounds = []; 454 | this.currentRoundId = 0; 455 | this.minAmount = 0; 456 | this.maxNumber = 0; 457 | this.numberOfBets = 0; 458 | this.prize = 0; 459 | this.remaining = 0; 460 | this.progress = 0; 461 | this.number = -1; 462 | this.tabbed = 0; 463 | this.rounds = []; 464 | var self = this; 465 | 466 | this.init = function () { 467 | self.rounds = []; 468 | $('#rounds_' + self.id).html(''); 469 | 470 | async.waterfall([ 471 | function(done) { 472 | console.log(self.id) 473 | self.contractInstance.getGameCurrentRoundId(self.id, function(error, result) { 474 | self.currentRoundId = result.valueOf(); 475 | done(error, result.valueOf()); 476 | }); 477 | }, 478 | function(currentRoundId, done) { 479 | self.contractInstance.getGamePrize(self.id, function(error, result){ 480 | self.prize = web3.fromWei(result.valueOf(), 'ether'); 481 | done(error, result.valueOf()); 482 | }); 483 | }, 484 | function(roundPrize, done) { 485 | self.contractInstance.getGameMinAmountByBet(self.id, function(error, result){ 486 | self.minAmount = web3.fromWei(result.valueOf(), 'ether'); 487 | done(error, self.minAmount); 488 | }); 489 | }, 490 | function(roundMinAmountByBet, done) { 491 | self.contractInstance.getGameMaxNumberOfBets(self.id, function(error, result){ 492 | self.maxNumber = result.valueOf(); 493 | done(error, result.valueOf()); 494 | }); 495 | }, 496 | function rounds(maxNumberOfBets, done) { 497 | for (var j = 0; j <= self.currentRoundId; j++) { 498 | self.rounds.push(new Round(self, j)); 499 | self.lotthereum.addRoundPlaceHolder(self.id, j); 500 | } 501 | done(null, self.currentRoundId); 502 | }, 503 | ], 504 | function (err) { 505 | if (err) { 506 | console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!'); 507 | console.error(err); 508 | } 509 | }); 510 | } 511 | 512 | this.init(); 513 | console.log('game.id >>>> ' + this.id); 514 | } 515 | 516 | function Round(game, roundId) { 517 | this.id = roundId; 518 | this.game = game; 519 | this.gameId = game.id; 520 | this.lotthereum = game.lotthereum; 521 | this.contractInstance = game.lotthereum.contractInstance; 522 | this.initialized = false; 523 | this.rendered = 0; 524 | this.tabbed = 0; 525 | var self = this; 526 | 527 | this.init = function (payload) { 528 | self.payload = payload; 529 | self.open = false; 530 | self.remaining = 0; 531 | self.progress = 0; 532 | self.number = -1; 533 | self.bets = []; 534 | 535 | async.waterfall([ 536 | function(done) { 537 | self.contractInstance.getGameRoundOpen(self.game.id, self.id, function(error, result){ 538 | self.open = result.valueOf(); 539 | done(error, result.valueOf()); 540 | }); 541 | }, 542 | function(isOpen, done) { 543 | self.contractInstance.getRoundNumber(self.game.id, self.id, function(error, result){ 544 | self.number = result.valueOf(); 545 | done(error, self.number); 546 | }); 547 | }, 548 | function(number, done) { 549 | self.contractInstance.getRoundNumberOfBets(self.game.id, self.id, function(error, result){ 550 | self.numberOfBets = result.valueOf(); 551 | console.log('self.game.maxNumber: '+ self.game.maxNumber) 552 | self.remaining = parseInt(self.game.maxNumber) - parseInt(self.numberOfBets); 553 | self.progress = parseFloat(100 * self.numberOfBets / self.game.maxNumber).toString(); 554 | for (var i = self.numberOfBets - 1; i >= 0; i--) { 555 | self.bets.push(new Bet(self, i)); 556 | } 557 | if (self.numberOfBets < 1) { 558 | self.initialized = true; 559 | self.lotthereum.isInitialized(self.gameId, self.id); 560 | } 561 | done(error, result.valueOf()); 562 | }); 563 | }, 564 | ], 565 | function (err) { 566 | if (err) { 567 | console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!'); 568 | console.error(err); 569 | } 570 | }); 571 | } 572 | this.init(); 573 | } 574 | 575 | function Bet(round, betId) { 576 | this.id = betId; 577 | this.roundId = round.id; 578 | this.round = round; 579 | this.contractInstance = round.contractInstance; 580 | this.lotthereum = round.lotthereum; 581 | this.origin = ''; 582 | this.amount = 0; 583 | this.bet = ''; 584 | this.transactionId = ''; 585 | this.confirmations = 0; 586 | this.initialized = false; 587 | var self = this; 588 | 589 | this.init = function () { 590 | async.waterfall([ 591 | function(done) { 592 | self.contractInstance.getRoundBetOrigin(self.round.game.id, self.round.id, self.id, function(error, result){ 593 | self.origin = result.valueOf(); 594 | done(error, self.origin); 595 | }); 596 | }, 597 | function(roundBetOrigin, done) { 598 | self.contractInstance.getRoundBetAmount(self.round.game.id, self.round.id, self.id, function(error, result){ 599 | self.amount = web3.fromWei(result.valueOf(), 'ether'); 600 | done(error, self.amount); 601 | }); 602 | }, 603 | function(amount, done) { 604 | self.contractInstance.getRoundBetNumber(self.round.game.id, self.round.id, self.id, function(error, result){ 605 | self.bet = result.valueOf(); 606 | done(error, self.bet); 607 | }); 608 | }, 609 | function(bet, done) { 610 | self.initialized = true; 611 | done(null, 'done!'); 612 | } 613 | ], 614 | function (err) { 615 | if(err) { 616 | console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!'); 617 | console.error(err); 618 | } else { 619 | self.round.game.lotthereum.isInitialized(self.round.game.id, self.round.id); 620 | } 621 | }); 622 | } 623 | this.init(); 624 | } --------------------------------------------------------------------------------