├── logs └── .gitkeep ├── VERSION ├── certs ├── .placeholder └── README ├── aux_configs └── README.md ├── scripts ├── blocknotify ├── clustertest.js ├── cli.js └── blocknotify.c ├── coins ├── denarius.json ├── pexacoin.json ├── bitcash.json ├── tragocoin.json ├── placeholder.json ├── ravencoin.json ├── taonacoin.json ├── mogwaicoin.json ├── rapturecoin.json └── securetag.json ├── docs ├── screenshots │ ├── home.png │ ├── minerstats.png │ ├── poolstats.png │ └── blockexplorer.png ├── DOCKER.md ├── TROUBLESHOOTING.md ├── CHANGELOG.md ├── TODO.md └── INSTALL.md ├── website ├── static │ ├── favicon.png │ ├── img │ │ ├── pow.png │ │ ├── JSON.png │ │ ├── favicon.png │ │ └── noto-hash-banner.png │ ├── icons │ │ ├── veco.png │ │ ├── nexton.png │ │ ├── qudex.png │ │ ├── bitcash.png │ │ ├── dixicoin.png │ │ ├── herbcoin.png │ │ ├── minexcoin.png │ │ ├── pexacoin.png │ │ ├── qbiccoin.png │ │ ├── ravencoin.png │ │ ├── securetag.png │ │ ├── taonacoin.png │ │ ├── tragocoin.png │ │ ├── unknown.png │ │ ├── wavicoin.png │ │ ├── mogwaicoin.png │ │ ├── placeholder.png │ │ ├── rapturecoin.png │ │ ├── wget-log │ │ └── url?sa=i │ ├── main.js │ ├── logo.svg │ ├── style.css │ ├── dashboard.js │ ├── admin.js │ ├── fontawesome.js │ ├── pool_stats.js │ ├── methods.js │ ├── stats.js │ ├── stat_tracker.js │ ├── nvd3.css │ └── miner_stats.js └── pages │ ├── api.html │ ├── mining_key.html │ ├── admin.html │ ├── stats.html │ ├── dashboard.html │ ├── tbs.html │ ├── news_example.html │ ├── workers.html │ ├── miner_stats.html │ ├── pools.html │ ├── history.html │ ├── pool_stats.html │ └── learn_more.html ├── lzCode_example.conf ├── matomoCode_example.conf ├── .gitignore ├── pool-stop.sh ├── pool-logs-watch.sh ├── pool-start.sh ├── pool-reset-stats.sh ├── pool-restart.sh ├── CONTRIBUTING.md ├── libs ├── workerapi.js ├── cliListener.js ├── logger.js ├── functions.js ├── mongoCompatibility.js ├── apiCoinWarz.js ├── shareProcessor.js ├── mposCompatibility.js ├── apiCryptsy.js ├── apiPoloniex.js ├── apiMintpal.js ├── apiBittrex.js └── api.js ├── install.sh ├── docker ├── build │ └── config.gypi ├── Dockerfile └── config │ ├── pool_configs │ └── litecoin_example.json │ └── config_example.json ├── pool_configs ├── pexacoin_example.json ├── placeholder_example.json └── tragocoin_example.json ├── package.json └── config_example.json /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.4 2 | -------------------------------------------------------------------------------- /certs/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aux_configs/README.md: -------------------------------------------------------------------------------- 1 | **DO NOT USE THIS DIRECTORY! 2 | -------------------------------------------------------------------------------- /scripts/blocknotify: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/scripts/blocknotify -------------------------------------------------------------------------------- /coins/denarius.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Denarius", 3 | "symbol": "D", 4 | "algorithm": "tribus" 5 | } 6 | -------------------------------------------------------------------------------- /docs/screenshots/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/docs/screenshots/home.png -------------------------------------------------------------------------------- /website/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/favicon.png -------------------------------------------------------------------------------- /website/static/img/pow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/img/pow.png -------------------------------------------------------------------------------- /website/static/icons/veco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/veco.png -------------------------------------------------------------------------------- /website/static/img/JSON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/img/JSON.png -------------------------------------------------------------------------------- /docs/screenshots/minerstats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/docs/screenshots/minerstats.png -------------------------------------------------------------------------------- /docs/screenshots/poolstats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/docs/screenshots/poolstats.png -------------------------------------------------------------------------------- /website/static/icons/nexton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/nexton.png -------------------------------------------------------------------------------- /website/static/icons/qudex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/qudex.png -------------------------------------------------------------------------------- /website/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/img/favicon.png -------------------------------------------------------------------------------- /docs/screenshots/blockexplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/docs/screenshots/blockexplorer.png -------------------------------------------------------------------------------- /website/static/icons/bitcash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/bitcash.png -------------------------------------------------------------------------------- /website/static/icons/dixicoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/dixicoin.png -------------------------------------------------------------------------------- /website/static/icons/herbcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/herbcoin.png -------------------------------------------------------------------------------- /website/static/icons/minexcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/minexcoin.png -------------------------------------------------------------------------------- /website/static/icons/pexacoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/pexacoin.png -------------------------------------------------------------------------------- /website/static/icons/qbiccoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/qbiccoin.png -------------------------------------------------------------------------------- /website/static/icons/ravencoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/ravencoin.png -------------------------------------------------------------------------------- /website/static/icons/securetag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/securetag.png -------------------------------------------------------------------------------- /website/static/icons/taonacoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/taonacoin.png -------------------------------------------------------------------------------- /website/static/icons/tragocoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/tragocoin.png -------------------------------------------------------------------------------- /website/static/icons/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/unknown.png -------------------------------------------------------------------------------- /website/static/icons/wavicoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/wavicoin.png -------------------------------------------------------------------------------- /lzCode_example.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/icons/mogwaicoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/mogwaicoin.png -------------------------------------------------------------------------------- /website/static/icons/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/placeholder.png -------------------------------------------------------------------------------- /website/static/icons/rapturecoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/icons/rapturecoin.png -------------------------------------------------------------------------------- /website/static/img/noto-hash-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EasyX-Community/easyNOMP/HEAD/website/static/img/noto-hash-banner.png -------------------------------------------------------------------------------- /matomoCode_example.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.bak 3 | *.rdb 4 | *.log 5 | 6 | .git/ 7 | .idea/ 8 | 9 | node_modules/ 10 | libs/BACKUP-OLD 11 | website/static/OLD 12 | 13 | website/pages/news.html 14 | 15 | certs/*.pem 16 | certs/*.crt 17 | certs/*.key 18 | logs/* 19 | 20 | package-lock.json 21 | ecosystem.config.js 22 | 23 | access-token 24 | config.json 25 | lzCode.conf 26 | matomoCode.conf 27 | 28 | pool_configs/*.json 29 | !pool_configs/*_example.json 30 | 31 | !.gitkeep 32 | -------------------------------------------------------------------------------- /coins/pexacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pexacoin", 3 | "symbol": "PEXA", 4 | "algorithm": "x16r", 5 | 6 | "wallet": "https://github.com/pexacoin/core", 7 | 8 | "explorer":"https://pexa.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "https://pexa.easyxpool.tk/api/getblock?txid=", 11 | "explorerGetBlock": "https://pexa.easyxpool.tk/block/", 12 | "explorerGetTX": "https://pexa.easyxpool.tk/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 1 16 | 17 | } -------------------------------------------------------------------------------- /coins/bitcash.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bitcash", 3 | "symbol": "BITC", 4 | "algorithm": "x16r", 5 | 6 | "wallet": "https://github.com/WillyTheCat/BitCash", 7 | 8 | "explorer":"https://bitc.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "https://bitc.easyxpool.tk/api/getblock?txid=", 11 | "explorerGetBlock": "https://bitc.easyxpool.tk/block/", 12 | "explorerGetTX": "https://bitc.easyxpool.tk/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 1 16 | 17 | } -------------------------------------------------------------------------------- /coins/tragocoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tragocoin", 3 | "symbol": "TRAGO", 4 | "algorithm": "x16r", 5 | 6 | "wallet": "https://www.tragocoin.com/#apps", 7 | 8 | "explorer":"https://trago.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "https://trago.easyxpool.tk/api/getblock?txid=", 11 | "explorerGetBlock": "https://trago.easyxpool.tk/block/", 12 | "explorerGetTX": "https://trago.easyxpool.tk/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 1 16 | 17 | } -------------------------------------------------------------------------------- /coins/placeholder.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Placeholder", 3 | "symbol": "PHL", 4 | "algorithm": "x16r", 5 | 6 | "wallet": "https://github.com/xagau/Placeholders-X16R", 7 | 8 | "explorer":"https://phl.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "https://phl.easyxpool.tk/api/getblock?txid=", 11 | "explorerGetBlock": "https://phl.easyxpool.tk/block/", 12 | "explorerGetTX": "https://phl.easyxpool.tk/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 1 16 | 17 | } -------------------------------------------------------------------------------- /coins/ravencoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ravencoin", 3 | "symbol": "RVN", 4 | "algorithm": "x16r", 5 | 6 | "wallet": "https://github.com/xagau/Placeholders-X16R", 7 | 8 | "explorer":"https://rvn.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "http://rvnhodl.com/api/getrawtransaction?decrypt=1&txid=", 11 | "explorerGetBlock": "http://rvnhodl.com/block/", 12 | "explorerGetTX": "http://rvnhodl.com/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 1 16 | 17 | } -------------------------------------------------------------------------------- /coins/taonacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Taonacoin", 3 | "symbol": "TNA", 4 | "algorithm": "x16r", 5 | 6 | "wallet": "https://github.com/xagau/Placeholders-X16R", 7 | 8 | "explorer":"https://taona.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "https://tna.easyxpool.tk:453/api/getblock?txid=", 11 | "explorerGetBlock": "https://tna.easyxpool.tk:453/block/", 12 | "explorerGetTX": "https://tna.easyxpool.tk:453/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 960 16 | 17 | } -------------------------------------------------------------------------------- /coins/mogwaicoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mogwaicoin", 3 | "symbol": "MOG", 4 | "algorithm": "neoscrypt", 5 | 6 | "wallet": "https://github.com/WillyTheCat/BitCash", 7 | 8 | "explorer":"https://mog.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "http://explorer.mogwaicoin.org/api/getblock?hash=", 11 | "explorerGetBlock": "http://explorer.mogwaicoin.org/block/", 12 | "explorerGetTX": "http://explorer.mogwaicoin.org/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 960 16 | 17 | } -------------------------------------------------------------------------------- /pool-stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "" 4 | echo "EasyNOMP Starting..." 5 | echo "" 6 | 7 | source ~/.bashrc 8 | source /etc/os-release 9 | 10 | ## who am i? ## 11 | SCRIPTNAME="$(readlink -f ${BASH_SOURCE[0]})" 12 | BASEDIR="$(dirname $SCRIPTNAME)" 13 | 14 | ## Okay, print it ## 15 | echo "Script name : $SCRIPTNAME" 16 | echo "Current working dir : $PWD" 17 | echo "Script location path (dir) : $BASEDIR" 18 | echo "" 19 | 20 | ~/.nvm/versions/node/v8.1.4/bin/pm2 stop pool 21 | 22 | echo "" 23 | echo "Done!" 24 | echo "" 25 | 26 | exit 0 -------------------------------------------------------------------------------- /coins/rapturecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rapturecoin", 3 | "symbol": "RAP", 4 | "algorithm": "neoscrypt", 5 | 6 | "wallet": "https://github.com/xagau/Placeholders-X16R", 7 | 8 | "explorer":"https://rap.easyxpool.tk/", 9 | 10 | "explorerGetBlockJSON": "http://explorer.our-rapture.com/api/getrawtransaction?txid=", 11 | "explorerGetBlock": "http://explorer.our-rapture.com/block/", 12 | "explorerGetTX": "http://explorer.our-rapture.com/tx/", 13 | 14 | "blockTime": 60, 15 | "blockChange": 960 16 | 17 | } -------------------------------------------------------------------------------- /website/pages/api.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |

  API DOCUMENTATION

7 | 8 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /certs/README: -------------------------------------------------------------------------------- 1 | This directory contains your keys and certificates. 2 | 3 | `privkey.pem` : the private key for your certificate. 4 | `fullchain.pem`: the certificate file used in most server software. 5 | `chain.pem` : used for OCSP stapling in Nginx >=1.3.7. 6 | `cert.pem` : will break many server configurations, and should not be used 7 | without reading further documentation (see link below). 8 | 9 | We recommend not moving these files. For more information, see the Certbot 10 | User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. 11 | -------------------------------------------------------------------------------- /coins/securetag.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "securetag", 3 | "symbol": "STG", 4 | "algorithm": "neoscrypt", 5 | 6 | "wallet": "https://github.com/xagau/Placeholders-X16R", 7 | 8 | "explorer":"https://stg.easyxpool.tk/", 9 | 10 | // "peerMagic": "9170caca", 11 | 12 | "explorerGetBlockJSON": "http://explorer.securetag.io:3001/api/getrawtransaction?txid=", 13 | "explorerGetBlock": "http://explorer.securetag.io:3001/block/", 14 | "explorerGetTX": "http://explorer.securetag.io:3001/tx/", 15 | 16 | "blockTime": 60, 17 | "blockChange": 960 18 | 19 | } -------------------------------------------------------------------------------- /scripts/clustertest.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster'); 2 | const http = require('http'); 3 | const numCPUs = require('os').cpus().length; 4 | 5 | if (cluster.isMaster) { 6 | masterProcess(); 7 | } else { 8 | childProcess(); 9 | } 10 | 11 | function masterProcess() { 12 | console.log(`Master ${process.pid} is running`); 13 | 14 | for (let i = 0; i < numCPUs; i++) { 15 | console.log(`Forking process number ${i}...`); 16 | cluster.fork(); 17 | } 18 | 19 | process.exit(); 20 | } 21 | 22 | function childProcess() { 23 | console.log(`Worker ${process.pid} started and finished`); 24 | 25 | process.exit(); 26 | } 27 | -------------------------------------------------------------------------------- /website/pages/mining_key.html: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 16 |

17 | This script run client-side (in your browser). For maximum security download the script and run it locally and 18 | offline in a modern web browser. 19 |

20 | 21 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /pool-logs-watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "" 4 | echo "EasyNOMP Watching Logs..." 5 | echo "" 6 | 7 | source ~/.bashrc 8 | source /etc/os-release 9 | 10 | ## who am i? ## 11 | SCRIPTNAME="$(readlink -f ${BASH_SOURCE[0]})" 12 | BASEDIR="$(dirname $SCRIPTNAME)" 13 | 14 | ## Okay, print it ## 15 | echo "Script name : $SCRIPTNAME" 16 | echo "Current working dir : $PWD" 17 | echo "Script location path (dir) : $BASEDIR" 18 | echo "" 19 | 20 | 21 | if [ "${1}" != "" ]; then 22 | watch -n1 -- "sudo tail -n1000 ~/.pm2/logs/pool-out.log | grep -i -a \"${1}\"" ; 23 | else 24 | ~/.nvm/versions/node/v8.1.4/bin/pm2 logs pool ; 25 | fi 26 | 27 | 28 | echo "" 29 | echo "Done!" 30 | echo "" 31 | 32 | exit 0 -------------------------------------------------------------------------------- /pool-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "" 4 | echo "EasyNOMP Starting..." 5 | echo "" 6 | 7 | source ~/.bashrc 8 | source /etc/os-release 9 | 10 | ## who am i? ## 11 | SCRIPTNAME="$(readlink -f ${BASH_SOURCE[0]})" 12 | BASEDIR="$(dirname $SCRIPTNAME)" 13 | 14 | ## Okay, print it ## 15 | echo "Script name : $SCRIPTNAME" 16 | echo "Current working dir : $PWD" 17 | echo "Script location path (dir) : $BASEDIR" 18 | echo "" 19 | 20 | ~/.nvm/versions/node/v8.1.4/bin/pm2 del pool 21 | 22 | ~/.nvm/versions/node/v8.1.4/bin/pm2 start --name pool node -- --optimize_for_size --max-old-space-size=4096 "${BASEDIR}/init.js" 23 | 24 | renice -n -18 -p $(pidof node) 25 | renice -n -18 -p $(pidof nodejs) 26 | 27 | echo "" 28 | echo "Done!" 29 | echo "" 30 | 31 | exit 0 -------------------------------------------------------------------------------- /pool-reset-stats.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "" 4 | echo "EasyNOMP Stats Resetting..." 5 | echo "" 6 | 7 | source ~/.bashrc 8 | source /etc/os-release 9 | 10 | ## who am i? ## 11 | SCRIPTNAME="$(readlink -f ${BASH_SOURCE[0]})" 12 | BASEDIR="$(dirname $SCRIPTNAME)" 13 | 14 | ## Okay, print it ## 15 | echo "Script name : $SCRIPTNAME" 16 | echo "Current working dir : $PWD" 17 | echo "Script location path (dir) : $BASEDIR" 18 | echo "" 19 | 20 | ~/.nvm/versions/node/v8.1.4/bin/pm2 del pool 21 | 22 | redis-cli DEL statHistory 23 | 24 | ~/.nvm/versions/node/v8.1.4/bin/pm2 start --name pool node -- --optimize_for_size --max-old-space-size=4096 "${BASEDIR}/init.js" 25 | 26 | renice -n -18 -p $(pidof node) 27 | renice -n -18 -p $(pidof nodejs) 28 | 29 | echo "" 30 | echo "Done!" 31 | echo "" 32 | 33 | exit 0 -------------------------------------------------------------------------------- /pool-restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "" 4 | echo "EasyNOMP Restarting..." 5 | echo "" 6 | 7 | source ~/.bashrc 8 | source /etc/os-release 9 | 10 | ## who am i? ## 11 | SCRIPTNAME="$(readlink -f ${BASH_SOURCE[0]})" 12 | BASEDIR="$(dirname $SCRIPTNAME)" 13 | 14 | ## Okay, print it ## 15 | echo "Script name : $SCRIPTNAME" 16 | echo "Current working dir : $PWD" 17 | echo "Script location path (dir) : $BASEDIR" 18 | echo "" 19 | 20 | #~/.nvm/versions/node/v8.1.4/bin/pm2 del pool 21 | 22 | #~/.nvm/versions/node/v8.1.4/bin/pm2 start --name pool node -- --optimize_for_size --max-old-space-size=4096 "${BASEDIR}/init.js" 23 | 24 | ~/.nvm/versions/node/v8.1.4/bin/pm2 restart pool 25 | 26 | renice -n -18 -p $(pidof node) 27 | renice -n -18 -p $(pidof nodejs) 28 | 29 | echo "" 30 | echo "Done!" 31 | echo "" 32 | 33 | exit 0 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Anyone can contribute to the project! 2 | 3 | 1) Fork the repository 4 | 2) Make changes 5 | 3) Push changes to your repository 6 | 4) Push changes to parent repository (make a pull request) 7 | 8 | As long as you are not changing default donation addresses, etc. and the code change is good we will merge it. 9 | 10 | ------- 11 | 12 | ## OPEN BOUNTIES: 13 | 14 | EasyNOMP needs an android & ios app, as well as a new design... 15 | 16 | - negotiate $ - Web UI design change (menu, charts, graphs, guages, etc.) 17 | 18 | - negotiate $ - NATIVE android app to access API and display stats in its own mobile design. APPS MUST be native, I will not accept web browser apps. 19 | 20 | You will need to send example work of yours (your portfolio) and screenshots of current projects. 21 | 22 | To apply please create an issue ticket! 23 | 24 | Thank you 25 | -------------------------------------------------------------------------------- /website/static/icons/wget-log: -------------------------------------------------------------------------------- 1 | --2019-07-27 01:09:35-- https://www.google.ca/url?sa=i 2 | Resolving www.google.ca (www.google.ca)... 172.217.164.227, 2607:f8b0:400b:801::2003 3 | Connecting to www.google.ca (www.google.ca)|172.217.164.227|:443... connected. 4 | HTTP request sent, awaiting response... 200 OK 5 | Length: unspecified [text/html] 6 | Saving to: ‘url?sa=i’ 7 | 8 | url?sa=i [<=> ] 0 --.-KB/s url?sa=i [ <=> ] 1.38K --.-KB/s in 0.001s 9 | 10 | 2019-07-27 01:09:35 (2.07 MB/s) - ‘url?sa=i’ saved [1408] 11 | 12 | -------------------------------------------------------------------------------- /docs/DOCKER.md: -------------------------------------------------------------------------------- 1 | ## Docker Instructions 2 | ------- 3 | 4 | ***NOTE:*** _LeshaCat will redo docker as soon as done with block explorer/etc_
5 | 6 | ------- 7 | ### Configure Pool 8 | 9 | Correct configs appropriately to your environment in docker directory 10 | 11 | - Inside THIS directory, create a "config" directory 12 | - ***mkdir config*** 13 | - ***cp -R ../coins/ config/*** 14 | - ***cp -R ../pool_configs/ config/*** 15 | - ***cp ../config_example.json config/config.json*** 16 | - Edit config/config.json, config/coins/*.json, and config/pool_configs/*.json 17 | 18 | #### Build Docker 19 | ------- 20 | ``` 21 | cd docker 22 | docker build -t nomp . 23 | docker run -d --name nomp -v $(pwd)/config:/opt/config nomp 24 | ``` 25 | 26 | You will need to expose some ports to make it accessible from outside. You can achieve this by adding option -p HOST_PORT:CONTAINER_PORT in the last step 27 | 28 | You can see the logs of the server with ```docker logs -f nomp```, or jump into container with ```docker exec -it nomp```. 29 | 30 | ***EOF*** 31 | -------------------------------------------------------------------------------- /website/static/main.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | 3 | var hotSwap = function(page, pushSate){ 4 | if (pushSate) history.pushState(null, null, '/' + page); 5 | $('.pure-menu-selected').removeClass('pure-menu-selected'); 6 | $('a[href="/' + page + '"]').parent().addClass('pure-menu-selected'); 7 | $.get("/get_page", {id: page}, function(data){ 8 | $('main').html(data); 9 | }, 'html') 10 | }; 11 | 12 | $('.hot-swapper').click(function(event){ 13 | if (event.which !== 1) return; 14 | var pageId = $(this).attr('href').slice(1); 15 | hotSwap(pageId, true); 16 | event.preventDefault(); 17 | return false; 18 | }); 19 | 20 | window.addEventListener('load', function() { 21 | setTimeout(function() { 22 | window.addEventListener("popstate", function(e) { 23 | hotSwap(location.pathname.slice(1)); 24 | }); 25 | }, 0); 26 | }); 27 | 28 | window.statsSource = new EventSource("/api/live_stats"); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /scripts/cli.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | var defaultPort = 17117; 4 | var defaultHost = '127.0.0.1'; 5 | 6 | var args = process.argv.slice(2); 7 | var params = []; 8 | var options = {}; 9 | 10 | for(var i = 0; i < args.length; i++){ 11 | if (args[i].indexOf('-') === 0 && args[i].indexOf('=') !== -1){ 12 | var s = args[i].substr(1).split('='); 13 | options[s[0]] = s[1]; 14 | } 15 | else 16 | params.push(args[i]); 17 | } 18 | 19 | var command = params.shift(); 20 | 21 | 22 | 23 | var client = net.connect(options.port || defaultPort, options.host || defaultHost, function () { 24 | client.write(JSON.stringify({ 25 | command: command, 26 | params: params, 27 | options: options 28 | }) + '\n'); 29 | }).on('error', function(error){ 30 | if (error.code === 'ECONNREFUSED') 31 | console.log('Could not connect to NOMP instance at ' + defaultHost + ':' + defaultPort); 32 | else 33 | console.log('Socket error ' + JSON.stringify(error)); 34 | }).on('data', function(data) { 35 | //responses flood console, lets ignore them :) 36 | //console.log(data.toString()); 37 | }).on('close', function () { 38 | }); 39 | -------------------------------------------------------------------------------- /website/static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /libs/workerapi.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var os = require('os'); 3 | 4 | 5 | function workerapi(listen) { 6 | var _this = this; 7 | var app = express(); 8 | var counters = { 9 | validShares : 0, 10 | validBlocks : 0, 11 | invalidShares : 0 12 | }; 13 | 14 | var lastEvents = { 15 | lastValidShare : 0 , 16 | lastValidBlock : 0, 17 | lastInvalidShare : 0 18 | }; 19 | 20 | app.get('/stats', function (req, res) { 21 | res.send({ 22 | "clients" : Object.keys(_this.poolObj.stratumServer.getStratumClients()).length, 23 | "counters" : counters, 24 | "lastEvents" : lastEvents 25 | }); 26 | }); 27 | 28 | 29 | this.start = function (poolObj) { 30 | this.poolObj = poolObj; 31 | this.poolObj.once('started', function () { 32 | app.listen(listen, function (lol) { 33 | console.log("LISTENING "); 34 | }); 35 | }) 36 | .on('share', function(isValidShare, isValidBlock, shareData) { 37 | var now = Date.now(); 38 | if (isValidShare) { 39 | counters.validShares ++; 40 | lastEvents.lastValidShare = now; 41 | if (isValidBlock) { 42 | counters.validBlocks ++; 43 | lastEvents.lastValidBlock = now; 44 | } 45 | } else { 46 | counters.invalidShares ++; 47 | lastEvents.lastInvalidShare = now; 48 | } 49 | }); 50 | } 51 | } 52 | 53 | 54 | 55 | module.exports = workerapi; 56 | 57 | -------------------------------------------------------------------------------- /libs/cliListener.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var net = require('net'); 3 | const logger = require('./logger.js').getLogger('CLI', 'system'); 4 | 5 | var listener = module.exports = function listener(host, port){ 6 | 7 | var _this = this; 8 | 9 | 10 | this.start = function(){ 11 | net.createServer(function(c) { 12 | 13 | var data = ''; 14 | try { 15 | c.on('data', function (d) { 16 | data += d; 17 | if (data.slice(-1) === '\n') { 18 | var message = JSON.parse(data); 19 | _this.emit('command', message.command, message.params, message.options, function(message){ 20 | c.end(message); 21 | }); 22 | } 23 | }); 24 | c.on('end', function () { 25 | 26 | }); 27 | c.on('error', function () { 28 | 29 | }); 30 | } 31 | catch(e){ 32 | logger.error('CLI listener failed to parse message %s', data); 33 | } 34 | 35 | }).listen(port, host, function() { 36 | logger.info('CLI listening on %s:%s', host, port) 37 | }); 38 | } 39 | 40 | }; 41 | 42 | listener.prototype.__proto__ = events.EventEmitter.prototype; 43 | -------------------------------------------------------------------------------- /website/static/icons/url?sa=i: -------------------------------------------------------------------------------- 1 | Redirect Notice
Redirect Notice
 The page you were on is trying to send you to an invalid URL.

 If you do not want to visit that page, you can return to the previous page.


-------------------------------------------------------------------------------- /website/pages/admin.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 18 | 19 |
20 |
21 | Password 22 | 23 | 24 | 25 | 28 | 29 | 30 |
31 |
32 | 33 |
34 | 35 |
36 | Administration 37 | 41 |
42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 | 50 |
-------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is the EasyNOMP install script. 4 | echo "EasyNOMP install script." 5 | echo "Please do NOT run as root, run as the pool user!" 6 | 7 | echo "Installing... Please wait!" 8 | 9 | sleep 3 10 | 11 | sudo add-apt-repository -y ppa:bitcoin/bitcoin 12 | 13 | sudo apt-get update 14 | sudo apt-get upgrade -y 15 | sudo apt-get dist-upgrade -y 16 | 17 | sudo apt-get install -y sudo git nano wget curl ntp build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev git npm nodejs nodejs-legacy libminiupnpc-dev redis-server software-properties-common fail2ban libdb4.8-dev libdb4.8++-dev 18 | 19 | sudo systemctl enable fail2ban 20 | sudo systemctl start fail2ban 21 | 22 | sudo systemctl enable redis-server 23 | sudo systemctl start redis-server 24 | 25 | sudo systemctl enable ntp 26 | sudo systemctl start ntp 27 | 28 | wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 29 | source ~/.bashrc 30 | 31 | nvm install v8.1.4 32 | nvm use v8.1.4 33 | npm update -g 34 | 35 | npm install -g pm2@latest 36 | npm install -g npm@latest 37 | 38 | git clone https://github.com/EasyX-Community/EasyNOMP.git $(pwd)/EasyNOMP 39 | 40 | cd EasyNOMP 41 | 42 | npm install 43 | 44 | npm update 45 | npm audit fix 46 | 47 | ./start-pool.sh 48 | 49 | 50 | echo "Installation completed!" 51 | echo "Please resume installation at the EasyNOMP Wiki: https://github.com/EasyX-Community/EasyNOMP/wiki" 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /docker/build/config.gypi: -------------------------------------------------------------------------------- 1 | # Do not edit. File was generated by node-gyp's "configure" step 2 | { 3 | "target_defaults": { 4 | "cflags": [], 5 | "default_configuration": "Release", 6 | "defines": [], 7 | "include_dirs": [], 8 | "libraries": [] 9 | }, 10 | "variables": { 11 | "asan": 0, 12 | "host_arch": "x64", 13 | "icu_gyp_path": "tools/icu/icu-system.gyp", 14 | "icu_small": "false", 15 | "node_byteorder": "little", 16 | "node_install_npm": "false", 17 | "node_prefix": "/usr", 18 | "node_release_urlbase": "", 19 | "node_shared_http_parser": "false", 20 | "node_shared_libuv": "true", 21 | "node_shared_openssl": "true", 22 | "node_shared_zlib": "true", 23 | "node_tag": "", 24 | "node_use_dtrace": "false", 25 | "node_use_etw": "false", 26 | "node_use_lttng": "false", 27 | "node_use_openssl": "true", 28 | "node_use_perfctr": "false", 29 | "openssl_fips": "", 30 | "openssl_no_asm": 0, 31 | "python": "/usr/bin/python", 32 | "target_arch": "x64", 33 | "uv_parent_path": "/deps/uv/", 34 | "uv_use_dtrace": "false", 35 | "v8_enable_gdbjit": 0, 36 | "v8_enable_i18n_support": 1, 37 | "v8_no_strict_aliasing": 1, 38 | "v8_optimized_debug": 0, 39 | "v8_random_seed": 0, 40 | "v8_use_snapshot": "true", 41 | "want_separate_host_toolset": 0, 42 | "nodedir": "/usr/include/nodejs", 43 | "copy_dev_lib": "true", 44 | "standalone_static_library": 1 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /libs/logger.js: -------------------------------------------------------------------------------- 1 | const {createLogger, format, transports} = require('winston'); 2 | const {splat, combine, timestamp, label, printf} = format; 3 | 4 | const config = require('../config.json'); 5 | if(!config) { 6 | throw new Error("Config file config.json does not exist") 7 | } 8 | 9 | const logLevel = config.logger ? config.logger.level || 'debug' : config.logLevel || 'debug'; 10 | require('winston-daily-rotate-file'); 11 | 12 | module.exports = { 13 | getLogger: function (loggerName, coin) { 14 | let transportz = [new transports.Console()]; 15 | if (config.logger && config.logger.file) { 16 | transportz.push( 17 | new transports.DailyRotateFile({ 18 | filename: config.logger.file, 19 | datePattern: 'YYYY-MM-DD', 20 | prepend: false, 21 | localTime: false, 22 | level: logLevel 23 | }) 24 | ); 25 | } 26 | return createLogger({ 27 | format: combine( 28 | splat(), 29 | label({label: {loggerName: loggerName, coin: coin}}), 30 | timestamp(), 31 | printf(info => { 32 | return `[${info.timestamp}] [${info.level}] [${info.label.coin}] [${info.label.loggerName}] : ${info.message}`; 33 | }) 34 | ), 35 | level: logLevel, 36 | transports: transportz, 37 | }); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /website/pages/stats.html: -------------------------------------------------------------------------------- 1 | 30 | 31 |
32 |
33 |
34 |
Top 5 Pool Workers
35 |
36 |
37 | 38 |
39 |
Top 5 Pool Hashrates (MH/s)
40 |
41 |
42 |
43 |
44 |
45 |
Top 5 Blocks Pending Per Pool
46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | RUN apt-get update 3 | RUN apt-get upgrade -y 4 | RUN apt-get dist-upgrade -y 5 | RUN apt-get install -y sudo git nano wget curl ntp build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev git npm nodejs nodejs-legacy libminiupnpc-dev redis-server software-properties-common fail2ban 6 | 7 | 8 | RUN add-apt-repository ppa:bitcoin/bitcoin 9 | RUN apt-get update 10 | RUN apt-get install libdb4.8-dev libdb4.8++-dev 11 | RUN systemctl enable redis-server 12 | RUN systemctl start redis-server 13 | RUN systemctl enable fail2ban 14 | RUN systemctl start fail2ban 15 | RUN systemctl enable ntp 16 | RUN systemctl start ntp 17 | RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 18 | RUN source ~/.bashrc 19 | RUN nvm install v8.1.4 20 | RUN nvm use v8.1.4 21 | RUN npm update -g 22 | RUN npm install -g pm2@latest 23 | RUN npm install -g npm@latest 24 | RUN pm2 init 25 | 26 | 27 | 28 | WORKDIR /opt/ 29 | RUN git clone https://github.com/leshacat/EasyNOMP.git 30 | 31 | WORKDIR /opt/EasyNOMP 32 | 33 | RUN npm install -g multi-hashing@latest 34 | RUN npm install 35 | RUN npm update 36 | RUN npm audit fix 37 | 38 | RUN rm config_example.json 39 | RUN rm -rf coins 40 | RUN rm -rf pool_configs 41 | RUN ln -s /opt/config/config.json /opt/BootNOMP/config.json 42 | RUN ln -s /opt/config/coins /opt/BootNOMP/coins 43 | RUN ln -s /opt/config/pool_configs /opt/BootNOMP/pool_configs 44 | 45 | CMD service redis-server restart; service fail2ban restart; service ntp restart; pm2 start init.js -i max --watch --name pool 46 | -------------------------------------------------------------------------------- /docs/TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Troubleshooting 3 | 4 | ------- 5 | ## 1) Update Pool Source (should be done monthly at minimum) 6 | ``` 7 | cd EasyNOMP 8 | git pull 9 | npm update -g 10 | npm --depth 9999 update 11 | npm audit fix 12 | ./pool-restart.sh 13 | ``` 14 | 15 | ------- 16 | ## 2) Startup on Boot 17 | ``` 18 | pm2 startup 19 | ``` 20 | Copy & paste the command (if it asks you to)
21 | Save the process list 22 | ``` 23 | pm2 save 24 | ``` 25 | 26 | ------- 27 | 28 | ## 3) Watching Pool Logs 29 | ``` 30 | ./pool-logs-watch.sh # Watch regular logs 31 | or 32 | ./pool-logs-watch.sh " BTCADDRESSTOSCANFOR " # Scan for a coin address 33 | or 34 | ./pool-logs-watch.sh "block>accepted>" # Scan for accepted blocks 35 | or 36 | ./pool-logs-watch.sh "block>rejected>" # Scan for a rejected blocks 37 | or 38 | ./pool-logs-watch.sh "share>accepted>" # Scan for accepted shares 39 | or 40 | ./pool-logs-watch.sh "share>rejected>" # Scan for rejected shares 41 | or 42 | ./pool-logs-watch.sh "DIFFICULTY>" # Scan for difficulty updates 43 | ``` 44 | 45 | ------- 46 | 47 | ### 4) RPC Work Queue Depth Exceeded: 48 | 49 | https://github.com/bitcoin/bitcoin/issues/14578
50 | https://github.com/foxer666/node-open-mining-portal/issues/106 (fixed in master)
51 | https://github.com/foxer666/node-open-mining-portal/issues/145 (still working on a fix)
52 | 53 | If you encounter "Work queue depth exceeded" enter into wallet config: 54 | ``` 55 | rpcworkqueue=100 56 | ``` 57 | Restart the wallet + pool 58 | 59 | ***This crypto is not lost, it is still in your pool wallet. It will payout on restart :)*** 60 | 61 | ------- 62 | 63 | ***EOF*** 64 | -------------------------------------------------------------------------------- /website/static/style.css: -------------------------------------------------------------------------------- 1 | html, button, input, select, textarea, .pure-g [class *= "pure-u"], .pure-g-r [class *= "pure-u"]{ 2 | font-family: 'Open Sans', sans-serif; 3 | } 4 | 5 | html{ 6 | background: #2d2d2d; 7 | overflow-y: scroll; 8 | } 9 | 10 | body{ 11 | display: flex; 12 | flex-direction: column; 13 | max-width: 1160px; 14 | margin: 0 auto; 15 | } 16 | 17 | header > .home-menu{ 18 | background: inherit !important; 19 | height: 54px; 20 | display: flex; 21 | } 22 | 23 | header > .home-menu > a.pure-menu-heading, header > .home-menu > ul, header > .home-menu > ul > li{ 24 | display: flex !important; 25 | align-items: center; 26 | justify-content: center; 27 | line-height: normal !important; 28 | } 29 | 30 | header > .home-menu > a.pure-menu-heading{ 31 | color: white; 32 | font-size: 1.5em;} 33 | 34 | header > .home-menu > ul > li > a{ 35 | color: #ced4d9; 36 | } 37 | 38 | header > .home-menu > ul > li > a:hover, header > .home-menu > ul > li > a:focus{ 39 | background: inherit !important; 40 | } 41 | 42 | header > .home-menu > ul > li > a:hover, header > .home-menu > ul > li.pure-menu-selected > a{ 43 | color: white; 44 | } 45 | 46 | main{ 47 | background-color: #ebf4fa; 48 | position: relative; 49 | } 50 | 51 | footer{ 52 | text-align: center; 53 | color: #b3b3b3; 54 | text-decoration: none; 55 | font-size: 0.8em; 56 | padding: 15px; 57 | line-height: 24px; 58 | } 59 | 60 | footer a{ 61 | color: #fff; 62 | text-decoration: none; 63 | } 64 | 65 | footer iframe{ 66 | vertical-align: middle; 67 | } 68 | 69 | .footer_container { 70 | display: flex; 71 | flex-direction: column; 72 | align-items: center; 73 | } 74 | -------------------------------------------------------------------------------- /website/pages/dashboard.html: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 |
37 |
38 |

Look Up Address

39 |
40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
Wallets
61 |
62 |
63 | 66 | -------------------------------------------------------------------------------- /website/pages/tbs.html: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {{ for(var pool in it.stats.pools) { }} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {{ } }} 64 |
PoolAlgoWorkersValid SharesInvalid SharesTotal BlocksPendingConfirmedOrphanedHashrate
{{=it.stats.pools[pool].name}}{{=it.stats.pools[pool].algorithm}}{{=Object.keys(it.stats.pools[pool].workers).length}}{{=it.stats.pools[pool].poolStats.validShares}}{{=it.stats.pools[pool].poolStats.invalidShares}}{{=it.stats.pools[pool].poolStats.validBlocks}}{{=it.stats.pools[pool].blocks.pending}}{{=it.stats.pools[pool].blocks.confirmed}}{{=it.stats.pools[pool].blocks.orphaned}}{{=it.stats.pools[pool].hashrateString}}
65 | -------------------------------------------------------------------------------- /libs/functions.js: -------------------------------------------------------------------------------- 1 | 2 | const anonymize = require('ip-anonymize'); 3 | 4 | const loggerFactory = require('./logger.js'); 5 | 6 | const logger = loggerFactory.getLogger('PoolWorker', 'system'); 7 | 8 | module.exports = { 9 | 10 | anonymizeIP: function (ipaddr) { 11 | 12 | var retval = ipaddr; 13 | 14 | var portalConfig = JSON.parse(process.env.portalConfig); 15 | 16 | if (portalConfig.logips && portalConfig.anonymizeips) { 17 | 18 | retval = anonymize(ipaddr, portalConfig.ipv4bits, portalConfig.ipv6bits); 19 | logger.silly("ANONIP>TRUE> before [%s] after [%s]", ipaddr, retval); 20 | 21 | } 22 | else if (!(portalConfig.logips)) { 23 | 24 | retval = "AnOnYmOuS!"; 25 | logger.debug("ANONIP>FULL> ipaddr [%s]", retval); 26 | 27 | } 28 | else { 29 | 30 | logger.debug("ANONIP>FALSE> ipaddr [%s]", retval); 31 | 32 | } 33 | 34 | return retval; 35 | 36 | }, 37 | 38 | secToDHMSStr: function (seconds) { 39 | 40 | retval = ""; 41 | 42 | var intDays = Math.floor(seconds / 86400) || 0; 43 | var intHrs = Math.floor((seconds % 86400) / 3600) || 0; 44 | var intMin = Math.floor(((seconds % 86400) % 3600) / 60) || 0; 45 | var intSec = Math.floor(((seconds % 86400) % 3600) % 60) || 0; 46 | 47 | if (intDays > 0) { retval = retval + intDays.toString() + "d "; } 48 | if (intDays > 0 || intHrs > 0) { retval = retval + intHrs.toString() + "h "; } 49 | retval = retval + intMin.toString() + "m "; 50 | retval = retval + intSec.toString() + "s"; 51 | 52 | return retval 53 | 54 | } 55 | 56 | }; -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ------- 4 | 5 | ### v1.1.4 (under development) 6 | * Forked from [1301313Y/BootNOMP](https://github.com/1301313Y/BootNOMP) by [LeshaCat](https://github.com/leshacat) 7 | * Forked node-stratum-pool from [foxer666/node-stratum-pool](https://github.com/foxer666/node-stratum-pool) 8 | * Forked node-multi-hashing from [1301313Y/node-multi-hashing](https://github.com/1301313Y/node-multi-hashing) 9 | * Many fixes inside node-stratum-pool & node-multi-hashing 10 | * Docker: Added dependencies, ntp & pm2@latest 11 | * Install Docs: Added dependencies, ntp & pm2@latest 12 | * Added coin config files / examples 13 | * Added donation addresses in config files 14 | * Added meta tag control in config files 15 | * Reworked Block Explorer (shows all coins) 16 | * Fix for https://github.com/foxer666/node-stratum-pool/commit/a07031808059a76a1fc61db9b10099155df1b6bb 17 | * Fix for https://github.com/foxer666/node-stratum-pool/pull/23/commits/67eace3468a77f7a585303fe8a1a2081eeba4096 18 | 19 | ### v1.0.0 20 | * Started versioning 21 | * Fixed payments functionality https://github.com/foxer666/node-stratum-pool/pull/6 22 | 23 | ### v1.0.1 24 | * Stratum module update https://github.com/foxer666/node-stratum-pool/pull/7 25 | 26 | ### v1.0.2 27 | * Added x16r (ravencoin) support 28 | 29 | ### v1.0.3 30 | * Logging switched to winston 31 | * https://github.com/foxer666/node-open-mining-portal/issues/36 32 | * https://github.com/foxer666/node-open-mining-portal/issues/37 33 | * Onexcoin 34 | 35 | ### v1.0.4 36 | * https://github.com/foxer666/node-open-mining-portal/issues/40 37 | 38 | ### v1.0.5 39 | * https://github.com/foxer666/node-open-mining-portal/issues/42 40 | 41 | ### v1.0.6 42 | * https://github.com/foxer666/node-open-mining-portal/issues/36 hotfix 43 | 44 | ### v1.0.7 45 | * https://github.com/foxer666/node-open-mining-portal/issues/36 hotfix 46 | 47 | ***EOF*** 48 | -------------------------------------------------------------------------------- /docker/config/pool_configs/litecoin_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "litecoin.json", 4 | 5 | "address": "n4jSe18kZMCdGcZqaYprShXW6EH1wivUK1", 6 | 7 | "rewardRecipients": { 8 | "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, 9 | "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 10 | }, 11 | 12 | "paymentProcessing": { 13 | "enabled": true, 14 | "paymentInterval": 20, 15 | "minimumPayment": 70, 16 | "daemon": { 17 | "host": "127.0.0.1", 18 | "port": 19332, 19 | "user": "testuser", 20 | "password": "testpass" 21 | } 22 | }, 23 | 24 | "ports": { 25 | "3008": { 26 | "diff": 8 27 | }, 28 | "3032": { 29 | "diff": 32, 30 | "varDiff": { 31 | "minDiff": 8, 32 | "maxDiff": 512, 33 | "targetTime": 15, 34 | "retargetTime": 90, 35 | "variancePercent": 30 36 | } 37 | }, 38 | "3256": { 39 | "diff": 256 40 | } 41 | }, 42 | 43 | "daemons": [ 44 | { 45 | "host": "127.0.0.1", 46 | "port": 19332, 47 | "user": "testuser", 48 | "password": "testpass" 49 | } 50 | ], 51 | 52 | "p2p": { 53 | "enabled": true, 54 | "host": "127.0.0.1", 55 | "port": 19333, 56 | "disableTransactions": true 57 | }, 58 | 59 | "mposMode": { 60 | "enabled": false, 61 | "host": "127.0.0.1", 62 | "port": 3306, 63 | "user": "me", 64 | "password": "mypass", 65 | "database": "ltc", 66 | "checkPassword": true, 67 | "autoCreateWorker": false 68 | }, 69 | 70 | "mongoMode": { 71 | "enabled": false, 72 | "host": "127.0.0.1", 73 | "user": "", 74 | "pass": "", 75 | "database": "ltc", 76 | "authMechanism": "DEFAULT" 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /scripts/blocknotify.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /* 11 | 12 | Contributed by Alex Petrov aka SysMan at sysman.net 13 | Updated by Alejandro Reyero - TodoJuegos.com 14 | 15 | Part of NOMP project 16 | Simple lightweight & fast - a more efficient block notify script in pure C. 17 | 18 | (may also work as coin switch) 19 | 20 | Platforms : Linux, BSD, Solaris (mostly OS independent) 21 | 22 | Build with: 23 | gcc blocknotify.c -o blocknotify 24 | 25 | 26 | Example usage in daemon coin.conf using default NOMP CLI port of 17117 27 | blocknotify="/bin/blocknotify 127.0.0.1:17117 dogecoin %s" 28 | 29 | 30 | 31 | */ 32 | 33 | 34 | int main(int argc, char **argv) 35 | { 36 | int sockfd,n; 37 | struct sockaddr_in servaddr, cliaddr; 38 | char sendline[1000]; 39 | char recvline[1000]; 40 | char host[200]; 41 | char *p, *arg, *errptr; 42 | int port; 43 | 44 | if (argc < 3) 45 | { 46 | // print help 47 | printf("NOMP pool block notify\n usage: \n"); 48 | exit(1); 49 | } 50 | 51 | strncpy(host, argv[1], (sizeof(host)-1)); 52 | p = host; 53 | 54 | if ( (arg = strchr(p,':')) ) 55 | { 56 | *arg = '\0'; 57 | 58 | errno = 0; // reset errno 59 | port = strtol(++arg, &errptr, 10); 60 | 61 | if ( (errno != 0) || (errptr == arg) ) 62 | { 63 | fprintf(stderr, "port number fail [%s]\n", errptr); 64 | } 65 | 66 | } 67 | 68 | snprintf(sendline, sizeof(sendline) - 1, "{\"command\":\"blocknotify\",\"params\":[\"%s\",\"%s\"]}\n", argv[2], argv[3]); 69 | 70 | sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 71 | bzero(&servaddr, sizeof(servaddr)); 72 | servaddr.sin_family = AF_INET; 73 | servaddr.sin_addr.s_addr = inet_addr(host); 74 | servaddr.sin_port = htons(port); 75 | connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 76 | 77 | int result = send(sockfd, sendline, strlen(sendline), 0); 78 | close(sockfd); 79 | 80 | if(result == -1) { 81 | printf("Error sending: %i\n", errno); 82 | exit(-1); 83 | } 84 | exit(0); 85 | } 86 | -------------------------------------------------------------------------------- /website/pages/news_example.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 | 8 |
9 | 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 | 36 | 37 | 38 |

Latest News:

2019/08/07 @ 0214 ESTTHIS FILE IS AN EXAMPLE FILE. You must copy EasyNOMP/websites/pages/news_example.html to EasyNOMP/websites/pages/news.html and edit it.
2019/08/07 @ 0214 ESTJust a friendly reminder that 0.25% fees will be added to PEXA & PHL on 2019/08/15.
2019/08/07 @ 0000 ESTThe bonus payment for the vardiff and stats problems has been sent! Check it out: here
2019/08/06 @ 1200 ESTWe have finally fixed the vardiff issue & statistics issue that required the statistics to be reset all the time! We will make a bonus payment to those who stuck around or came back shortly after the fix.
35 | Thank you & we apologize for inconvenience!
39 |
40 |
41 |
42 |
-------------------------------------------------------------------------------- /website/pages/workers.html: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 |
37 | 38 | {{ for(var pool in it.stats.pools) { }} 39 | 40 |
41 |
{{=it.stats.pools[pool].name}}
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {{ for(var worker in it.stats.pools[pool].workers) { }} 54 | 55 | {{ let workerInfo = worker.split('.'); }} 56 | {{ if (workerInfo.length === 2) { }} 57 | {{ let worker = workerInfo[0]; }} 58 | {{ } }} 59 | 60 | {{var workerstat = it.stats.pools[pool].workers[worker];}} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {{ } }} 72 | 73 |
AddressSharesInvalid sharesEfficiencyHashrate
{{=worker}}{{=Math.floor(workerstat.shares)}}{{=Math.floor(workerstat.invalidshares)}}{{? workerstat.shares > 0}} {{=Math.floor(10000 * workerstat.shares / (workerstat.shares + workerstat.invalidshares)) / 100}}% {{??}} 0% {{?}}{{=workerstat.hashrateString}}
74 |
75 |
76 | {{ } }} 77 | -------------------------------------------------------------------------------- /pool_configs/pexacoin_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "pexacoin.json", 4 | 5 | "address": "YOUR_POOLS_WALLET_ADDRESS", 6 | 7 | "donateaddress": "XF5zeEsu63n32xT4duAG853Wtk1o6tbk7v", 8 | 9 | "rewardRecipients": { 10 | "YOUR_FEE1_WALLET_ADDRESS": 1.5, 11 | "XF5zeEsu63n32xT4duAG853Wtk1o6tbk7v": 0.1 12 | }, 13 | 14 | "paymentProcessing": { 15 | "enabled": true, 16 | "schema": "PROP", 17 | "paymentInterval": 300, 18 | "minimumPayment": 0.25, 19 | "maxBlocksPerPayment": 10, 20 | "minConf": 30, 21 | "coinPrecision": 8, 22 | "daemon": { 23 | "host": "127.0.0.1", 24 | "port": 6005, 25 | "user": "MY_RPC_USER", 26 | "password": "MY_RPC_PASS" 27 | } 28 | }, 29 | 30 | "ports": { 31 | "3008": { 32 | "varDiff": { 33 | "minDiff": 0, 34 | "maxDiff": 15, 35 | "targetTime": 10, 36 | "retargetTime": 60, 37 | "variancePercent": 30, 38 | "maxJump": 25 39 | } 40 | }, 41 | "3032": { 42 | "varDiff": { 43 | "minDiff": 0, 44 | "maxDiff": 600, 45 | "targetTime": 10, 46 | "retargetTime": 60, 47 | "variancePercent": 30, 48 | "maxJump": 50 49 | } 50 | }, 51 | "3256": { 52 | "varDiff": { 53 | "minDiff": 1000, 54 | "maxDiff": 7000, 55 | "targetTime": 10, 56 | "retargetTime": 60, 57 | "variancePercent": 30, 58 | "maxJump": 50 59 | } 60 | } 61 | }, 62 | 63 | "daemons": [ 64 | { 65 | "host": "127.0.0.1", 66 | "port": 6005, 67 | "user": "MY_RPC_USER", 68 | "password": "MY_RPC_PASS" 69 | } 70 | ], 71 | 72 | "p2p": { 73 | "enabled": false, 74 | "host": "127.0.0.1", 75 | "port": 19333, 76 | "disableTransactions": true 77 | }, 78 | 79 | "mposMode": { 80 | "enabled": false, 81 | "host": "127.0.0.1", 82 | "port": 3306, 83 | "user": "me", 84 | "password": "mypass", 85 | "database": "ltc", 86 | "checkPassword": true, 87 | "autoCreateWorker": false 88 | }, 89 | 90 | "mongoMode": { 91 | "enabled": false, 92 | "host": "127.0.0.1", 93 | "user": "", 94 | "pass": "", 95 | "database": "ltc", 96 | "authMechanism": "DEFAULT" 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /pool_configs/placeholder_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "placeholder.json", 4 | 5 | "address": "YOUR_POOLS_WALLET_ADDRESS", 6 | 7 | "donateaddress": "FD2RVJBxwBvNLnN4EzHpHNTho2PQyDLeAt", 8 | 9 | "rewardRecipients": { 10 | "YOUR_FEE1_WALLET_ADDRESS": 1.5, 11 | "FD2RVJBxwBvNLnN4EzHpHNTho2PQyDLeAt": 0.1 12 | }, 13 | 14 | "paymentProcessing": { 15 | "enabled": true, 16 | "schema": "PROP", 17 | "paymentInterval": 300, 18 | "minimumPayment": 0.25, 19 | "maxBlocksPerPayment": 10, 20 | "minConf": 30, 21 | "coinPrecision": 8, 22 | "daemon": { 23 | "host": "127.0.0.1", 24 | "port": 2200, 25 | "user": "MY_RPC_USER", 26 | "password": "MY_RPC_PASS" 27 | } 28 | }, 29 | 30 | "ports": { 31 | "4008": { 32 | "varDiff": { 33 | "minDiff": 0, 34 | "maxDiff": 15, 35 | "targetTime": 10, 36 | "retargetTime": 60, 37 | "variancePercent": 30, 38 | "maxJump": 25 39 | } 40 | }, 41 | "4032": { 42 | "varDiff": { 43 | "minDiff": 0, 44 | "maxDiff": 600, 45 | "targetTime": 10, 46 | "retargetTime": 60, 47 | "variancePercent": 30, 48 | "maxJump": 50 49 | } 50 | }, 51 | "4256": { 52 | "varDiff": { 53 | "minDiff": 1000, 54 | "maxDiff": 7000, 55 | "targetTime": 10, 56 | "retargetTime": 60, 57 | "variancePercent": 30, 58 | "maxJump": 50 59 | } 60 | } 61 | }, 62 | 63 | "daemons": [ 64 | { 65 | "host": "192.168.1.20", 66 | "port": 2200, 67 | "user": "MY_RPC_USER", 68 | "password": "MY_RPC_PASS" 69 | } 70 | ], 71 | 72 | "p2p": { 73 | "enabled": false, 74 | "host": "127.0.0.1", 75 | "port": 19333, 76 | "disableTransactions": true 77 | }, 78 | 79 | "mposMode": { 80 | "enabled": false, 81 | "host": "127.0.0.1", 82 | "port": 3306, 83 | "user": "me", 84 | "password": "mypass", 85 | "database": "ltc", 86 | "checkPassword": true, 87 | "autoCreateWorker": false 88 | }, 89 | 90 | "mongoMode": { 91 | "enabled": false, 92 | "host": "127.0.0.1", 93 | "user": "", 94 | "pass": "", 95 | "database": "ltc", 96 | "authMechanism": "DEFAULT" 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /pool_configs/tragocoin_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "tragocoin.json", 4 | 5 | "address": "YOUR_POOLS_WALLET_ADDRESS", 6 | 7 | "donateaddress": "Ta26x9axaDQWaV2bt2z8Dk3R3dN7gHw9b6", 8 | 9 | "rewardRecipients": { 10 | "YOUR_FEE1_WALLET_ADDRESS": 1.5, 11 | "Ta26x9axaDQWaV2bt2z8Dk3R3dN7gHw9b6": 0.1 12 | }, 13 | 14 | "paymentProcessing": { 15 | "enabled": true, 16 | "schema": "PROP", 17 | "paymentInterval": 300, 18 | "minimumPayment": 0.25, 19 | "maxBlocksPerPayment": 10, 20 | "minConf": 30, 21 | "coinPrecision": 8, 22 | "daemon": { 23 | "host": "192.168.1.20", 24 | "port": 2205, 25 | "user": "MY_RPC_USER", 26 | "password": "MY_RPC_PASS" 27 | } 28 | }, 29 | 30 | "ports": { 31 | "10008": { 32 | "varDiff": { 33 | "minDiff": 0, 34 | "maxDiff": 15, 35 | "targetTime": 10, 36 | "retargetTime": 60, 37 | "variancePercent": 30, 38 | "maxJump": 25 39 | } 40 | }, 41 | "10032": { 42 | "varDiff": { 43 | "minDiff": 0, 44 | "maxDiff": 600, 45 | "targetTime": 10, 46 | "retargetTime": 60, 47 | "variancePercent": 30, 48 | "maxJump": 50 49 | } 50 | }, 51 | "10256": { 52 | "varDiff": { 53 | "minDiff": 1000, 54 | "maxDiff": 9000, 55 | "targetTime": 10, 56 | "retargetTime": 60, 57 | "variancePercent": 30, 58 | "maxJump": 50 59 | } 60 | } 61 | }, 62 | 63 | "daemons": [ 64 | { 65 | "host": "192.168.1.20", 66 | "port": 2205, 67 | "user": "MY_RPC_USER", 68 | "password": "MY_RPC_PASS" 69 | } 70 | ], 71 | 72 | "p2p": { 73 | "enabled": false, 74 | "host": "127.0.0.1", 75 | "port": 19333, 76 | "disableTransactions": true 77 | }, 78 | 79 | "mposMode": { 80 | "enabled": false, 81 | "host": "127.0.0.1", 82 | "port": 3306, 83 | "user": "me", 84 | "password": "mypass", 85 | "database": "ltc", 86 | "checkPassword": true, 87 | "autoCreateWorker": false 88 | }, 89 | 90 | "mongoMode": { 91 | "enabled": false, 92 | "host": "127.0.0.1", 93 | "user": "", 94 | "pass": "", 95 | "database": "ltc", 96 | "authMechanism": "DEFAULT" 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | ## Todo list 2 | 3 | ------- 4 | 5 | **NOTE:** Ordered by priority 6 | **NOTE:** Order messed up... More of list of ideas. 7 | 8 | **[ ?=question ] [ !=important ] [ I=inprogress ] [ T=testing ] [ X=completed ]** 9 | 10 | || ToDo Item Description | 11 | | ------------- | ------------- | 12 | | **(!IT)** | Install script | 13 | | **(!)** | Add node-merged-pool: https://github.com/UNOMP/node-merged-pool | 14 | | **(!)** | Auto-Detect if walletd is out of date and warn admin | 15 | | **(!)** | Installer script | 16 | | **(!IT)** | Block Explorer | 17 | | **(!IT)** | * Show blocks from all coins | 18 | | **(!)** | * Immature blocks / Confirmation count | 19 | | **(X)** | Payment Explorer | 20 | | **(X)** | * Show Payment from all coins | 21 | ||| 22 | | **(!)** | More statistics | 23 | | **(!)** | * BTC Value of block at time of mining | 24 | | **( )** | * Average blocks per day, week, month for each coin | 25 | | **( )** | * Average time to find block | 26 | ||| 27 | | **( )** | Pools page (pools.html) needs sortable datatable [ [LeshaCat](https://github.com/leshacat) ] | 28 | | **(!I)** | Payments Module | 29 | | **(!)** | * Rewrite payments module + tests [ [LeshaCat:](https://github.com/leshacat) Was looking at this module... (rewrite, add features, still pondering) ] | 30 | | **(X)** | * Fix batch payments bug from issue https://github.com/foxer666/node-open-mining-portal/issues/106 | 31 | ||| 32 | | **(!)** | Add option to make an manual payments rather than automatic (for emergency payments & pools with big network diff) [ [LeshaCat:](https://github.com/leshacat) Will put control in Admin Panel that uses RPC call ] | 33 | | **( )** | New frontend [ Are you changing the design? ] | 34 | | **( )** | Move modules in one project | 35 | | **(?)** | Write different log level at one time [ What do you mean...? ] | 36 | | **(IT)** | Docker Image / Documentation [ [LeshaCat](https://github.com/leshacat) ] | 37 | | **(IT)** | Documentation [ [LeshaCat](https://github.com/leshacat) ] | 38 | | **(!)** | MySQL/MariaDB/MongoDB options (one or all) as replacement for Redis ? | 39 | | **( )** | Auto-Detect daemon.conf settings (if in default location) [ [LeshaCat](https://github.com/leshacat) ] | 40 | 41 | ------ 42 | 43 | Mark your name beside tasks you wish to work on, or are currently working on :) 44 | 45 | If you are OK with it I can knock a bit off this list, and you could handle graphics/stats/modules/redis/init.js and code I can't figure out 46 | 47 | Also I don't care to learn Redis, so adding new statistics or database options you will need to do. 48 | 49 | I have many other ideas but not going to list here. 50 | 51 | LeshaCat CURRENT Main focus: Connect string features c=PEXA, d=30, m=solo, etc. Payment processor, API, Logging 52 | 53 | ***EOF*** 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EasyNOMP", 3 | "version": "1.1.4", 4 | "description": "An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool", 5 | "keywords": [ 6 | "stratum", 7 | "mining", 8 | "pool", 9 | "server", 10 | "poolserver", 11 | "bitcoin", 12 | "litecoin", 13 | "scrypt" 14 | ], 15 | "homepage": "https://github.com/EasyX-Community/EasyNOMP", 16 | "bugs": { 17 | "url": "https://github.com/EasyX-Community/EasyNOMP/issues" 18 | }, 19 | "license": "GPL-2.0", 20 | "author": "Leshacat", 21 | "contributors": [ 22 | "leshacat", 23 | "devnulled", 24 | "foxer666", 25 | "vekexasia", 26 | "TheSeven", 27 | "Kris Klosterman" 28 | ], 29 | "main": "init.js", 30 | "bin": { 31 | "block-notify": "./scripts/blockNotify.js" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/EasyX-Community/EasyNOMP.git" 36 | }, 37 | "dependencies": { 38 | "3.npm": "^1.0.0", 39 | "asn1": "^0.2.4", 40 | "async": "^2.6.1", 41 | "bcrypt-pbkdf": "^1.0.2", 42 | "bignum": "^0.13.0", 43 | "bignumber.js": "^6.0.0", 44 | "body-parser": "^1.18.3", 45 | "chart.js": "^2.7.3", 46 | "chartjs-plugin-zoom": "^0.6.6", 47 | "cluster": "^0.7.7", 48 | "color-name": "^1.1.4", 49 | "colors": "^1.3.3", 50 | "combined-stream": "^1.0.7", 51 | "compression": "^1.7.3", 52 | "dateformat": "^2.2.0", 53 | "dot": "^1.1.2", 54 | "ecc-jsbn": "^0.1.2", 55 | "exponential-moving-average": "^1.0.0", 56 | "express": "^4.16.4", 57 | "form-data": "^2.3.3", 58 | "http-errors": "^1.7.1", 59 | "ip-anonymize": "^0.1.0", 60 | "ls": "^0.2.1", 61 | "mime-types": "^2.1.21", 62 | "moment": "^2.23.0", 63 | "multi-hashing": "git://github.com/EasyX-Community/node-multi-hashing.git", 64 | "mysql": "^2.16.0", 65 | "newrelic": "^5.1.0", 66 | "node-json-minify": "^1.0.0", 67 | "node-multi-hashing": "0.0.1", 68 | "node-watch": "^0.5.9", 69 | "nonce": "^1.0.4", 70 | "redis": "^2.7.1", 71 | "redis-commands": "^1.4.0", 72 | "request": "^2.88.0", 73 | "safe-buffer": "^5.1.2", 74 | "semver": "^5.7.1", 75 | "sma": "^0.1.1", 76 | "sshpk": "^1.15.2", 77 | "stratum-pool": "git://github.com/EasyX-Community/node-stratum-pool.git", 78 | "winston": "^3.1.0", 79 | "winston-daily-rotate-file": "^3.5.1", 80 | "winston-transport": "^4.3.0" 81 | }, 82 | "engines": { 83 | "node": ">=8.1.4" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Pool Installation Instructions 2 | 3 | ------- 4 | 5 | ### Installer Script 6 | You know we like things easy, so we've created this handy installer script for you! 7 | Please be aware, this script is not yet tested, and may not even work! 8 | Simply create a user for the pool to run as, and log in to that user. 9 | Run this command 10 | 11 | ``` 12 | # Please be careful, this script may or may not work :P 13 | # Manual install preferable. 14 | # 15 | git clone https://github.com/EasyX-Community/EasyNOMP.git ; cd EasyNOMP ; ./install.sh ; 16 | ``` 17 | **Done!** 18 | 19 | ------- 20 | ### Install Requirements 21 | ``` 22 | sudo apt-get update 23 | sudo apt-get upgrade -y 24 | sudo apt-get dist-upgrade -y 25 | sudo apt-get install -y sudo git nano wget curl ntp build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev git npm nodejs nodejs-legacy libminiupnpc-dev redis-server software-properties-common fail2ban 26 | sudo add-apt-repository ppa:bitcoin/bitcoin 27 | sudo apt-get update 28 | sudo apt-get install libdb4.8-dev libdb4.8++-dev 29 | sudo systemctl enable fail2ban 30 | sudo systemctl start fail2ban 31 | sudo systemctl enable redis-server 32 | sudo systemctl start redis-server 33 | sudo systemctl enable ntp 34 | sudo systemctl start ntp 35 | wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash 36 | source ~/.bashrc 37 | nvm install 8 38 | nvm use 8 39 | npm update -g 40 | npm install -g pm2@latest 41 | npm install -g npm@latest 42 | ``` 43 | 44 | ------- 45 | ### Flush Redis Data 46 | **If you are switching from z-nomp, your old statistics and payouts data is incompatable - to wipe it please run:** 47 | ``` 48 | redis-cli FLUSHALL 49 | ``` 50 | **WARNING: You will loose statistics and payout data.** 51 | 52 | ------- 53 | ### Install Pool 54 | ``` 55 | git clone https://github.com/EasyX-Community/EasyNOMP.git 56 | cd EasyNOMP 57 | npm install 58 | npm update 59 | npm audit fix 60 | ./pool-start.sh 61 | ``` 62 | 63 | ------- 64 | ### Watching Pool Logs 65 | ``` 66 | ./pool-logs-watch.sh # Watch regular logs 67 | 68 | ./pool-logs-watch.sh " BTCADDRESSTOSCANFOR " # Scan for a coin address 69 | 70 | ./pool-logs-watch.sh "block>accepted>" # Scan for accepted blocks 71 | 72 | ./pool-logs-watch.sh "block>rejected>" # Scan for a rejected blocks 73 | 74 | ./pool-logs-watch.sh "share>accepted>" # Scan for accepted shares 75 | 76 | ./pool-logs-watch.sh "share>rejected>" # Scan for rejected shares 77 | 78 | ./pool-logs-watch.sh "DIFFICULTY>" # Scan for difficulty updates 79 | ``` 80 | 81 | ------- 82 | ### Restarting Pool 83 | ``` 84 | ./pool-restart 85 | ``` 86 | 87 | ------- 88 | ### Startup on Boot 89 | ``` 90 | pm2 startup 91 | ``` 92 | Copy & paste the command (if it asks you to)
93 | Save the process list 94 | ``` 95 | pm2 save 96 | ``` 97 | 98 | ------- 99 | ### Update Pool Source (should be done monthly at minimum) 100 | ``` 101 | cd EasyNOMP 102 | git pull 103 | rm -rf node_modules 104 | npm update -g 105 | npm update -g npm 106 | npm install 107 | npm audit fix 108 | ./pool-restart.sh 109 | ``` 110 | 111 | ***EOF*** 112 | -------------------------------------------------------------------------------- /libs/mongoCompatibility.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient, 2 | f = require('util').format; 3 | 4 | const loggerFactory = require('./logger.js'); 5 | 6 | module.exports = function (poolConfig) { 7 | 8 | var mongoConfig = poolConfig.mongoMode; 9 | var coin = poolConfig.coin.name; 10 | 11 | var mongoConfig = { 12 | host: poolConfig.mongoMode.host, 13 | database: poolConfig.mongoMode.database, 14 | user: encodeURIComponent(poolConfig.mongoMode.user), 15 | pass: encodeURIComponent(poolConfig.mongoMode.pass), 16 | authMechanism: poolConfig.mongoMode.authMechanism ? poolConfig.mongoMode.authMechanism : "DEFAULT" 17 | }; 18 | 19 | let logger = loggerFactory.getLogger('MongoCompatibility', coin); 20 | 21 | var logIdentify = 'MongoDB'; 22 | var logComponent = coin; 23 | 24 | var connectionURL = ""; 25 | 26 | if (mongoConfig.user && mongoConfig.pass) { 27 | connectionURL = f('mongodb://%s:%s@%s:27017/myproject?authMechanism=%s', mongoConfig.user, mongoConfig.pass, mongoConfig.host, mongoConfig.database, mongoConfig.authMechanism); 28 | } else { 29 | connectionURL = f('mongodb://%s:27017/%s', mongoConfig.host, mongoConfig.database); 30 | } 31 | 32 | //TODO: PRIORITY: Check to see if collection exists and create it if not 33 | 34 | var mongoInsert = function (collectionName, data, errCallback, successCallback) { 35 | MongoClient.connect(connectionURL, function (err, db) { 36 | 37 | var collection = db.collection(collectionName); 38 | 39 | collection.insert(data, function (err, result) { 40 | if (err) { 41 | errCallback(err); 42 | //TODO: do we stop it from moving on here? 43 | } 44 | 45 | successCallback(result); 46 | db.close(); //TODO: does this work? does it get called before the above callback can do whatever with result? 47 | }); 48 | 49 | }); 50 | }; 51 | 52 | this.handleShare = function (isValidShare, isValidBlock, shareData) { 53 | 54 | var dbData = { 55 | rem_host: shareData.ip, 56 | worker: shareData.worker, 57 | valid_share: isValidShare ? 'Y' : 'N', 58 | valid_block: isValidBlock ? 'Y' : 'N', 59 | difficulty: shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1), 60 | reason: typeof(shareData.error) === 'undefined' ? null : shareData.error, 61 | solution: shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '') 62 | }; 63 | 64 | mongoInsert('shares', dbData, 65 | function (err) { 66 | logger.error('Insert error when adding share: %s', JSON.stringify(err)); 67 | }, 68 | function (result) { 69 | logger.debug('Share inserted, result = %s', JSON.stringify(result)); 70 | }); 71 | 72 | }; 73 | 74 | this.handleDifficultyUpdate = function (workerName, diff) { 75 | //TODO: 76 | }; 77 | 78 | this.handleAuth = function (workerName, password, authCallback) { 79 | //TODO: 80 | }; 81 | 82 | 83 | }; -------------------------------------------------------------------------------- /docker/config/config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "logLevel": "debug", 3 | "logColors": true, 4 | 5 | "cliHost": "127.0.0.1", 6 | "cliPort": 17117, 7 | 8 | "clustering": { 9 | "enabled": true, 10 | "forks": "auto" 11 | }, 12 | 13 | "defaultPoolConfigs": { 14 | "blockRefreshInterval": 1000, 15 | "jobRebroadcastTimeout": 55, 16 | "connectionTimeout": 600, 17 | "emitInvalidBlockHashes": false, 18 | "validateWorkerUsername": true, 19 | "tcpProxyProtocol": false, 20 | "banning": { 21 | "enabled": true, 22 | "time": 600, 23 | "invalidPercent": 50, 24 | "checkThreshold": 500, 25 | "purgeInterval": 300 26 | }, 27 | "redis": { 28 | "host": "127.0.0.1", 29 | "port": 6379 30 | } 31 | }, 32 | 33 | "website": { 34 | "enabled": true, 35 | "host": "0.0.0.0", 36 | "port": 80, 37 | "stratumHost": "cryppit.com", 38 | "stats": { 39 | "updateInterval": 60, 40 | "historicalRetention": 43200, 41 | "hashrateWindow": 300 42 | }, 43 | "adminCenter": { 44 | "enabled": true, 45 | "password": "password" 46 | } 47 | }, 48 | 49 | "redis": { 50 | "host": "127.0.0.1", 51 | "port": 6379 52 | }, 53 | 54 | "switching": { 55 | "switch1": { 56 | "enabled": false, 57 | "algorithm": "sha256", 58 | "ports": { 59 | "3333": { 60 | "diff": 10, 61 | "varDiff": { 62 | "minDiff": 16, 63 | "maxDiff": 512, 64 | "targetTime": 15, 65 | "retargetTime": 90, 66 | "variancePercent": 30 67 | } 68 | } 69 | } 70 | }, 71 | "switch2": { 72 | "enabled": false, 73 | "algorithm": "scrypt", 74 | "ports": { 75 | "4444": { 76 | "diff": 10, 77 | "varDiff": { 78 | "minDiff": 16, 79 | "maxDiff": 512, 80 | "targetTime": 15, 81 | "retargetTime": 90, 82 | "variancePercent": 30 83 | } 84 | } 85 | } 86 | }, 87 | "switch3": { 88 | "enabled": false, 89 | "algorithm": "x11", 90 | "ports": { 91 | "5555": { 92 | "diff": 0.001, 93 | "varDiff": { 94 | "minDiff": 0.001, 95 | "maxDiff": 1, 96 | "targetTime": 15, 97 | "retargetTime": 60, 98 | "variancePercent": 30 99 | } 100 | } 101 | } 102 | } 103 | }, 104 | 105 | "profitSwitch": { 106 | "enabled": false, 107 | "updateInterval": 600, 108 | "depth": 0.90, 109 | "usePoloniex": true, 110 | "useCryptsy": true, 111 | "useMintpal": true, 112 | "useBittrex": true 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /website/static/dashboard.js: -------------------------------------------------------------------------------- 1 | var statData; 2 | var poolKeys; 3 | 4 | //this function is executed when the page's dom is loaded 5 | // assumes jQuery is loaded already 6 | $(function(){ 7 | var dataTable = $("#walletTable").DataTable({ 8 | "order": [[ 0, "desc" ]], 9 | "pageLength": 10, 10 | "bLengthChange": false, 11 | "iDisplayLength": 10, 12 | "height": 200, 13 | "pageLength": 10, 14 | "pagingType": "full_numbers", 15 | "lengthMenu": [ 25, 50, 100, 150, 300, 500 ] 16 | }); 17 | var cachedWallets = Cookies.get('wallets'); 18 | if(cachedWallets && cachedWallets.length > 0){ 19 | cachedWallets = JSON.parse(cachedWallets); 20 | for(w in cachedWallets) { 21 | var wallet = cachedWallets[w].split(','); 22 | var coin = wallet[0]; 23 | var address = wallet[1]; 24 | dataTable.row.add([ 25 | " " + address + "", 26 | "" 27 | ]).draw(false); 28 | $('#' + address).click(function(event) { 29 | if(confirm("Are you sure you want to delete address: " + address)){ 30 | cachedWallets.splice(w, 1); 31 | Cookies.remove('wallets'); 32 | Cookies.set('wallets', cachedWallets, { expires: 30 }); 33 | location.reload(); 34 | } 35 | }); 36 | } 37 | } 38 | //binds the myFormOnSubmit method below to run as part of your form's onsubmit method 39 | $('#searchButton').click(myFormOnSubmit); 40 | 41 | //runs when the form is trying to submit 42 | function myFormOnSubmit(event) { 43 | var f = $(this); 44 | // note, you have to match on attribute selectors 45 | // you may want to give each of these fields an id=".." attribute as well to select against #IdName 46 | var search = $('#searchBar').val(); 47 | var isValid = false; 48 | 49 | var coin = ""; 50 | var wallets = Cookies.get('wallets'); 51 | var stored = false; 52 | if(wallets) { 53 | wallets = JSON.parse(wallets); 54 | for(w in wallets) { 55 | if(wallets[w].split(',')[1] === search) { 56 | stored = true; 57 | break; 58 | } 59 | } 60 | } 61 | if(stored){ 62 | alert('Address Already Stored!'); 63 | event.preventDefault(); //stop submit 64 | return; 65 | } 66 | if(!wallets){ 67 | wallets = []; 68 | } 69 | $.each(statData.pools, function(i, v) { 70 | if(!isValid){ 71 | for(worker in v.workers){ 72 | worker = worker.split('.')[0]; 73 | if(worker === search){ 74 | isValid = true; 75 | wallets.push(String(i + ',' + worker)); 76 | break; 77 | } 78 | } 79 | } 80 | }); 81 | if (!isValid) { 82 | alert('No Address Found!'); 83 | event.preventDefault(); //stop submit 84 | return; 85 | } else { 86 | Cookies.remove('wallets'); 87 | Cookies.set('wallets', wallets, { expires: 30 }); 88 | } 89 | } 90 | }); 91 | 92 | 93 | 94 | $.getJSON('/api/stats', function(data) { 95 | statData = data; 96 | }); 97 | -------------------------------------------------------------------------------- /libs/apiCoinWarz.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.0.1', 11 | PUBLIC_API_URL = 'http://www.coinwarz.com/v1/api/profitability/?apikey=YOUR_API_KEY&algo=all', 12 | USER_AGENT = 'nomp/node-open-mining-portal' 13 | 14 | // Constructor 15 | function Cryptsy(key, secret){ 16 | // Generate headers signed by this user's key and secret. 17 | // The secret is encapsulated and never exposed 18 | this._getPrivateHeaders = function(parameters){ 19 | var paramString, signature; 20 | 21 | if (!key || !secret){ 22 | throw 'CoinWarz: Error. API key and secret required'; 23 | } 24 | 25 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 26 | paramString = Object.keys(parameters).sort().map(function(param){ 27 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 28 | }).join('&'); 29 | 30 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 31 | 32 | return { 33 | Key: key, 34 | Sign: signature 35 | }; 36 | }; 37 | } 38 | 39 | // If a site uses non-trusted SSL certificates, set this value to false 40 | Cryptsy.STRICT_SSL = true; 41 | 42 | // Helper methods 43 | function joinCurrencies(currencyA, currencyB){ 44 | return currencyA + '_' + currencyB; 45 | } 46 | 47 | // Prototype 48 | CoinWarz.prototype = { 49 | constructor: CoinWarz, 50 | 51 | // Make an API request 52 | _request: function(options, callback){ 53 | if (!('headers' in options)){ 54 | options.headers = {}; 55 | } 56 | 57 | options.headers['User-Agent'] = USER_AGENT; 58 | options.json = true; 59 | options.strictSSL = CoinWarz.STRICT_SSL; 60 | 61 | request(options, function(err, response, body) { 62 | callback(err, body); 63 | }); 64 | 65 | return this; 66 | }, 67 | 68 | // Make a public API request 69 | _public: function(parameters, callback){ 70 | var options = { 71 | method: 'GET', 72 | url: PUBLIC_API_URL, 73 | qs: parameters 74 | }; 75 | 76 | return this._request(options, callback); 77 | }, 78 | 79 | 80 | ///// 81 | 82 | 83 | // PUBLIC METHODS 84 | 85 | getTicker: function(callback){ 86 | var parameters = { 87 | method: 'marketdatav2' 88 | }; 89 | 90 | return this._public(parameters, callback); 91 | }, 92 | 93 | getOrderBook: function(currencyA, currencyB, callback){ 94 | var parameters = { 95 | command: 'returnOrderBook', 96 | currencyPair: joinCurrencies(currencyA, currencyB) 97 | }; 98 | 99 | return this._public(parameters, callback); 100 | }, 101 | 102 | getTradeHistory: function(currencyA, currencyB, callback){ 103 | var parameters = { 104 | command: 'returnTradeHistory', 105 | currencyPair: joinCurrencies(currencyA, currencyB) 106 | }; 107 | 108 | return this._public(parameters, callback); 109 | }, 110 | 111 | 112 | //// 113 | 114 | return CoinWarz; 115 | }(); 116 | -------------------------------------------------------------------------------- /website/static/admin.js: -------------------------------------------------------------------------------- 1 | var docCookies = { 2 | getItem: function (sKey) { 3 | return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null; 4 | }, 5 | setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) { 6 | if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; } 7 | var sExpires = ""; 8 | if (vEnd) { 9 | switch (vEnd.constructor) { 10 | case Number: 11 | sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd; 12 | break; 13 | case String: 14 | sExpires = "; expires=" + vEnd; 15 | break; 16 | case Date: 17 | sExpires = "; expires=" + vEnd.toUTCString(); 18 | break; 19 | } 20 | } 21 | document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : ""); 22 | return true; 23 | }, 24 | removeItem: function (sKey, sPath, sDomain) { 25 | if (!sKey || !this.hasItem(sKey)) { return false; } 26 | document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : ""); 27 | return true; 28 | }, 29 | hasItem: function (sKey) { 30 | return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie); 31 | } 32 | }; 33 | 34 | var password = docCookies.getItem('password'); 35 | 36 | 37 | function showLogin(){ 38 | $('#adminCenter').hide(); 39 | $('#passwordForm').show(); 40 | } 41 | 42 | function showAdminCenter(){ 43 | $('#passwordForm').hide(); 44 | $('#adminCenter').show(); 45 | } 46 | 47 | function tryLogin(){ 48 | apiRequest('pools', {}, function(response){ 49 | showAdminCenter(); 50 | displayMenu(response.result) 51 | }); 52 | } 53 | 54 | function displayMenu(pools){ 55 | $('#poolList').after(Object.keys(pools).map(function(poolName){ 56 | return '
  • ' + poolName + '
  • '; 57 | }).join('')); 58 | } 59 | 60 | function apiRequest(func, data, callback){ 61 | var httpRequest = new XMLHttpRequest(); 62 | httpRequest.onreadystatechange = function(){ 63 | if (httpRequest.readyState === 4 && httpRequest.responseText){ 64 | if (httpRequest.status === 401){ 65 | docCookies.removeItem('password'); 66 | $('#password').val(''); 67 | showLogin(); 68 | alert('Incorrect Password'); 69 | } 70 | else{ 71 | var response = JSON.parse(httpRequest.responseText); 72 | callback(response); 73 | } 74 | } 75 | }; 76 | httpRequest.open('POST', '/api/admin/' + func); 77 | data.password = password; 78 | httpRequest.setRequestHeader('Content-Type', 'application/json'); 79 | httpRequest.send(JSON.stringify(data)); 80 | } 81 | 82 | if (password){ 83 | tryLogin(); 84 | } 85 | else{ 86 | showLogin(); 87 | } 88 | 89 | $('#passwordForm').submit(function(event){ 90 | event.preventDefault(); 91 | password = $('#password').val(); 92 | if (password){ 93 | if ($('#remember').is(':checked')) 94 | docCookies.setItem('password', password, Infinity); 95 | else 96 | docCookies.setItem('password', password); 97 | tryLogin(); 98 | } 99 | return false; 100 | }); 101 | -------------------------------------------------------------------------------- /website/pages/miner_stats.html: -------------------------------------------------------------------------------- 1 | 70 | 71 |
    72 |
    73 |

    Account Details

    74 |
    75 |
    76 |

    77 | Total Immature:
    78 | Total Balance:
    79 | Total Paid:

    81 |
    82 |
    83 |
    84 |
    85 |
    86 |
    87 |
    88 |
    89 |
    90 |
    91 |
    92 |
    93 |
    94 |
    95 |
    96 |
    97 |
    98 |
    Worker Hashrate History
    Colors are random! If you don't like the ones given, simply refresh the page!
    99 |
    100 |
    101 |
    102 | 103 |
     

    104 | 105 |
    106 |
    107 |
    108 |
    109 | 110 | 121 | -------------------------------------------------------------------------------- /website/static/fontawesome.js: -------------------------------------------------------------------------------- 1 | window.FontAwesomeKitConfig = {"asyncLoading":{"enabled":true},"autoA11y":{"enabled":true},"baseUrl":"https://kit-free.fontawesome.com","license":"free","method":"css","minify":{"enabled":true},"v4shim":{"enabled":true},"version":"latest"}; 2 | !function(){!function(){if(!(void 0===window.Element||"classList"in document.documentElement)){var e,t,n,i=Array.prototype,o=i.push,a=i.splice,s=i.join;r.prototype={add:function(e){this.contains(e)||(o.call(this,e),this.el.className=this.toString())},contains:function(e){return-1!=this.el.className.indexOf(e)},item:function(e){return this[e]||null},remove:function(e){if(this.contains(e)){for(var t=0;t x[1]))); 8 | poolHashrateChart = createDefaultLineChart( 9 | document.getElementById("poolHashChart").getContext('2d'), 10 | [{ 11 | label: 'Actual', 12 | fill: false, 13 | data: stats.hashrate.map(x => { 14 | return { 15 | t: x[0], 16 | y: getScaledHashrate(x[1], maxScale[2]) 17 | } 18 | }), 19 | borderWidth: 2, 20 | backgroundColor: '#348EA9', 21 | borderColor: '#348EA9' 22 | }, 23 | { 24 | label: 'Averaged', 25 | fill: false, 26 | data: stats.averagedHashrate.map(x => { 27 | return { 28 | t: x[0], 29 | y: getScaledHashrate(x[1], maxScale[2]) 30 | } 31 | }), 32 | borderWidth: 2, 33 | backgroundColor: '#E81D62', 34 | borderColor: '#E81D62' 35 | }], 36 | 'Time', 37 | maxScale[1] 38 | ); 39 | poolWorkerChart = createLineChart( 40 | document.getElementById("poolWorkerChart").getContext('2d'), 41 | [{ 42 | label: 'Actual', 43 | fill: false, 44 | data: stats.workers.map(x => { 45 | return { 46 | t: x[0], 47 | y: x[1] 48 | } 49 | }), 50 | borderWidth: 2, 51 | backgroundColor: '#0061B5', 52 | borderColor: '#0061B5' 53 | }, 54 | { 55 | label: 'Averaged', 56 | fill: false, 57 | data: stats.averagedWorkers.map(x => { 58 | return { 59 | t: x[0], 60 | y: x[1] 61 | } 62 | }), 63 | borderWidth: 2, 64 | backgroundColor: '#FF9400', 65 | borderColor: '#FF9400' 66 | }], 67 | 'Time', 68 | 'Workers', 69 | { 70 | beginAtZero: true, 71 | fixedStepSize: 1 72 | } 73 | ); 74 | poolBlockChart = createLineChart( 75 | document.getElementById("blockChart").getContext('2d'), 76 | [{ 77 | label: 'Currently Pending', 78 | fill: true, 79 | steppedLine: true, 80 | data: stats.blocks.map(x => { 81 | return { 82 | t: x[0], 83 | y: x[1] 84 | } 85 | }), 86 | borderWidth: 1, 87 | backgroundColor: '#FBA41F', 88 | borderColor: '#FBA41F' 89 | }], 90 | 'Time', 91 | 'Blocks', 92 | { 93 | beginAtZero: true, 94 | fixedStepSize: 1 95 | } 96 | ); 97 | } 98 | 99 | $.getJSON('/api/pool_stats', function(data) { 100 | if (document.hidden) return; 101 | //Add pool to tracker 102 | addPoolToTracker(data, poolName, function() { 103 | displayCharts(); 104 | }); 105 | }); 106 | 107 | statsSource.addEventListener('message', function(e) { 108 | var stats = JSON.parse(e.data); 109 | updatePoolData(stats, poolName, function(pool) { 110 | var max = Math.max.apply(null, pool.hashrate.map(x => x[1])); 111 | var pair = getReadableHashRatePair(max); 112 | var hash = getScaledHashrate(poolName in stats.pools ? stats.pools[poolName].hashrate : 0, pair[2]); 113 | $("#validShares").text(poolName in stats.pools ? stats.pools[poolName].poolStats.validShares : 0); 114 | $("#poolHashRate").text((!isNaN(hash) ? hash : 0) + ' ' + (pair[1] ? pair[1] : 'H/s')); 115 | $("#poolWorkers").text(poolName in stats.pools ? stats.pools[poolName].workerCount : 0); 116 | $("#pendingBlocks").text(poolName in stats.pools ? stats.pools[poolName].blocks.pending : 0); 117 | $("#confirmedBlocks").text(poolName in stats.pools ? stats.pools[poolName].blocks.confirmed : 0); 118 | var time = stats.time * 1000; 119 | var avg = pool.averagedHashrate; 120 | addChartData(poolHashrateChart, poolHashrateChart.data.datasets[0], {t: time, y: hash}, false); 121 | addChartData(poolHashrateChart, poolHashrateChart.data.datasets[1], {t: time, y: getScaledHashrate(avg[avg.length - 1][1], pair[2])}, true); 122 | addChartData(poolBlockChart, poolBlockChart.data.datasets[0], {t: time, y: poolName in stats.pools ? stats.pools[poolName].blocks.pending : 0}, true); 123 | }); 124 | }, false); 125 | -------------------------------------------------------------------------------- /website/static/methods.js: -------------------------------------------------------------------------------- 1 | function calculateExpMovingAvg(mArray, mRange) { 2 | var k = 2/ (mRange + 1); 3 | // first item is just the same as the first item in the input 4 | emaArray = [[mArray[0][0], mArray[0][1]]]; 5 | // for the rest of the items, they are computed with the previous one 6 | for (var i = 1; i < mArray.length; i++) { 7 | var height = mArray[i][1] * k + emaArray[i - 1][1] * (1 - k); 8 | emaArray.push([mArray[i][0], height]); 9 | } 10 | return emaArray; 11 | } 12 | 13 | function capFirst(s) { 14 | return s.charAt(0).toUpperCase() + s.slice(1); 15 | } 16 | 17 | function getRandomInt(min, max) { 18 | return Math.floor(Math.random() * (max - min)) + min; 19 | } 20 | 21 | function generateName(){ 22 | var name1 = ['raging', 'mad', 'hashing', 'cool', 'rich', 'honorable', 'king', 23 | 'fast', 'killer', 'sweet']; 24 | 25 | var name2 = ['cromulon', 'computer', 'hasher', 'PC', 'rig', 'miner', 'otter', 26 | 'cronenberg', 'gazorpazorp']; 27 | 28 | var name = name1[Math.floor(Math.random() * name1.length)].toLowerCase() + name2[Math.floor(Math.random() * name2.length)].toLowerCase(); 29 | return name; 30 | 31 | } 32 | 33 | function getRandomColor() { 34 | var letters = '0123456789ABCDEF'.split(''); 35 | var color = '#'; 36 | for (var i = 0; i < 6; i++ ) { 37 | color += letters[Math.floor(Math.random() * 16)]; 38 | } 39 | return color; 40 | } 41 | 42 | function getRandomPastelColor() { 43 | var r = (Math.round(Math.random() * 127) + 127).toString(16); 44 | var g = (Math.round(Math.random() * 127) + 127).toString(16); 45 | var b = (Math.round(Math.random() * 127) + 127).toString(16); 46 | return '#' + r + g + b; 47 | } 48 | 49 | function addChartData(chart, dataset, data, update) { 50 | dataset.data.shift(); 51 | dataset.data.push(data); 52 | if(update){ 53 | chart.update(); 54 | } 55 | } 56 | 57 | this.getReadableHashRate = function(hashrate) { 58 | hashrate = (hashrate * 1000000); 59 | if(hashrate < 1000000){ 60 | hashrate = hashrate * 100000; 61 | } 62 | var i = Math.max(0, Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1)); 63 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1); 64 | return hashrate.toFixed(2); 65 | }; 66 | 67 | this.getScaledHashrate = function(hashrate, i) { 68 | hashrate = (hashrate * 1000000); 69 | if(hashrate < 1000000){ 70 | hashrate = hashrate * 100000; 71 | } 72 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1); 73 | return hashrate.toFixed(2); 74 | }; 75 | 76 | this.getReadableHashRateString = function(hashrate) { 77 | hashrate = (hashrate * 1000000); 78 | if(hashrate < 1000000){ 79 | hashrate = hashrate * 100000; 80 | } 81 | var byteUnits = [' H/s', ' KH/s', ' MH/s', ' GH/s', ' TH/s', ' PH/s']; 82 | var i = Math.max(0, Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1)); 83 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1); 84 | 85 | return hashrate.toFixed(2) + ' ' + byteUnits[i]; 86 | }; 87 | 88 | this.getReadableHashRatePair = function(hashrate) { 89 | hashrate = (hashrate * 1000000); 90 | if(hashrate < 1000000){ 91 | hashrate = hashrate * 100000; 92 | } 93 | var byteUnits = [' H/s', ' KH/s', ' MH/s', ' GH/s', ' TH/s', ' PH/s']; 94 | var i = Math.max(0, Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1)); 95 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1); 96 | 97 | return [hashrate.toFixed(2), byteUnits[i], i]; 98 | }; 99 | 100 | function createDefaultLineChart(ctx, datasets, xLabel, yLabel) { 101 | return createLineChart(ctx, datasets, xLabel, yLabel, { beginAtZero: true }); 102 | } 103 | 104 | function createLineChart(ctx, datasets, xLabel, yLabel, ticks) { 105 | return new Chart(ctx, { 106 | type: 'line', 107 | data: { 108 | datasets: datasets 109 | }, 110 | options: { 111 | spanGaps: true, 112 | animation: { 113 | easing: 'easeInExpo', 114 | duration: 1000, 115 | xAxis: true, 116 | yAxis: true, 117 | }, 118 | responsive: true, 119 | maintainAspectRatio: false, 120 | elements: { 121 | point: { radius: 0 } 122 | }, 123 | scales: { 124 | xAxes: [{ 125 | type: 'time' 126 | }], 127 | yAxes: [{ 128 | ticks: ticks, 129 | display: true, 130 | scaleLabel: { 131 | display: true, 132 | labelString: yLabel 133 | } 134 | }] 135 | } 136 | } 137 | }); 138 | } 139 | -------------------------------------------------------------------------------- /libs/shareProcessor.js: -------------------------------------------------------------------------------- 1 | var redis = require('redis'); 2 | var Stratum = require('stratum-pool'); 3 | 4 | const loggerFactory = require('./logger.js'); 5 | 6 | const logger = loggerFactory.getLogger('ShareProcessor', 'system'); 7 | /* 8 | This module deals with handling shares when in internal payment processing mode. It connects to a redis 9 | database and inserts shares with the database structure of: 10 | 11 | key: coin_name + ':' + block_height 12 | value: a hash with.. 13 | key: 14 | 15 | */ 16 | 17 | 18 | 19 | module.exports = function(poolConfig){ 20 | 21 | var redisConfig = poolConfig.redis; 22 | 23 | var coin = poolConfig.coin.name; 24 | var forkId = process.env.forkId; 25 | let logger = loggerFactory.getLogger(`ShareProcessor [:${forkId}]`, coin); 26 | 27 | 28 | var logSystem = 'Pool'; 29 | var logComponent = coin; 30 | var logSubCat = 'Thread ' + (parseInt(forkId) + 1); 31 | 32 | var connection = redis.createClient(redisConfig.port, redisConfig.host); 33 | 34 | connection.on('ready', function(){ 35 | logger.debug('Share processing setup with redis (' + redisConfig.host + 36 | ':' + redisConfig.port + ')'); 37 | }); 38 | connection.on('error', function(err){ 39 | logger.error(logSystem, logComponent, logSubCat, 'Redis client had an error: ' + JSON.stringify(err)) 40 | }); 41 | connection.on('end', function(){ 42 | logger.error(logSystem, logComponent, logSubCat, 'Connection to redis database has been ended'); 43 | }); 44 | 45 | connection.info(function(error, response){ 46 | if (error){ 47 | logger.error(logSystem, logComponent, logSubCat, 'Redis version check failed'); 48 | return; 49 | } 50 | var parts = response.split('\r\n'); 51 | var version; 52 | var versionString; 53 | for (var i = 0; i < parts.length; i++){ 54 | if (parts[i].indexOf(':') !== -1){ 55 | var valParts = parts[i].split(':'); 56 | if (valParts[0] === 'redis_version'){ 57 | versionString = valParts[1]; 58 | version = parseFloat(versionString); 59 | break; 60 | } 61 | } 62 | } 63 | if (!version){ 64 | logger.error(logSystem, logComponent, logSubCat, 'Could not detect redis version - but be super old or broken'); 65 | } 66 | else if (version < 2.6){ 67 | logger.error(logSystem, logComponent, logSubCat, "You're using redis version " + versionString + " the minimum required version is 2.6. Follow the damn usage instructions..."); 68 | } 69 | }); 70 | 71 | 72 | this.handleShare = function(isValidShare, isValidBlock, shareData){ 73 | 74 | var redisCommands = []; 75 | 76 | if (isValidShare){ 77 | redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]); 78 | redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]); 79 | } 80 | else{ 81 | redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]); 82 | } 83 | /* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it 84 | doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to 85 | generate hashrate for each worker and pool. */ 86 | var dateNow = Date.now(); 87 | var hashrateData = [ isValidShare ? shareData.difficulty : -shareData.difficulty, shareData.worker, dateNow]; 88 | redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); 89 | 90 | if (isValidBlock){ 91 | 92 | redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]); 93 | 94 | redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); 95 | 96 | redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]); 97 | 98 | } 99 | else if (shareData.blockHash){ 100 | 101 | redisCommands.push(['hincrby', coin + ':stats', 'invalidBlocks', 1]); 102 | 103 | } 104 | 105 | connection.multi(redisCommands).exec(function(err, replies){ 106 | if (err) 107 | logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err)); 108 | }); 109 | 110 | 111 | }; 112 | 113 | }; 114 | -------------------------------------------------------------------------------- /website/pages/pools.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Quick Start Commands (GPU Algorithms):

    3 |
    4 | ccminer -a <ALGORITHM> -o stratum+tcp://{{=it.portalConfig.website.stratumHost}}:<PORT> -u <WALLET.WORKER> -p <ANYTHING> 5 |
    6 |
    7 | sgminer -k <ALGORITHM> -o stratum+tcp://{{=it.portalConfig.website.stratumHost}}:<PORT> -u <WALLET.WORKER> -p <ANYTHING> 8 |
    9 |
    10 | t-rex -a <ALGORITHM> -o stratum+tcp://{{=it.portalConfig.website.stratumHost}}:<PORT> -u <WALLET.WORKER> -p <ANYTHING> 11 |
    12 |

    Doesn't make sense? Head over to Getting Started for more help!

    13 |
    14 |
    15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {{ var sortedPools = it.stats.pools; }} 36 | 37 | 38 | 39 | {{ for(var pool in it.stats.pools) { }} 40 | 41 | 45 | 46 | 47 | 48 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {{ var blocktime = it.stats.pools[pool].blockTime || 0; }} 69 | {{ var blockchange = it.stats.pools[pool].blockChange || 0; }} 70 | {{ var blockheight = it.stats.pools[pool].poolStats.networkBlocks || 0; }} 71 | 72 | {{ var changehours = Number.parseFloat((((blockchange) - (blockheight % blockchange)) / blocktime)).toFixed(2) || 0; }} 73 | {{ var changeblocks = ((blockchange) - (blockheight % blockchange)) || 0; }} 74 | 75 | 76 | 77 | {{ var total = 0.0; }} 78 | {{ var rewardRecipients = it.stats.pools[pool].rewardRecipients || {}; }} 79 | {{ for (var r in rewardRecipients) { }} 80 | {{ total += rewardRecipients[r]; }} 81 | {{ } }} 82 | 83 | 84 | 85 | 86 | 87 | {{ } }} 88 | 89 | 90 |
    Coin 19 | SymbolAlgoPortsN. Hash RateHash RateMaturePendingWorkersDifficultyChange InFeeDonations:
    42 | 43 | icon {{=it.stats.pools[pool].name.charAt(0).toUpperCase() + it.stats.pools[pool].name.slice(1)}} 44 | {{=it.stats.pools[pool].symbol}}{{=it.stats.pools[pool].algorithm}} 49 | {{ for(var pool2 in it.stats.pools) { }} 50 | {{ if(pool2 != pool) continue; }} 51 | {{=Object.keys(it.poolsConfigs[pool2].ports)}} 52 | {{ break; }} 53 | {{ } }} 54 | {{=it.stats.pools[pool].poolStats.networkSolsString}}{{=it.stats.pools[pool].hashrateString}}{{=it.stats.pools[pool].blocks.confirmed}}{{=it.stats.pools[pool].blocks.pending}}{{=it.stats.pools[pool].workerCount}}{{=Number(Math.round(it.stats.pools[pool].poolStats.networkDiff + 'e' + 4) + 'e-' + 4)}}{{=changeblocks}} blocks (appx. {{=changehours}} hours){{=total}}%{{=(it.poolsConfigs[pool].donateaddress||'none')}}
    91 |
    92 | -------------------------------------------------------------------------------- /config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "poolname": "My Cool Pool", 4 | 5 | "devmode": false, 6 | "devmodePayMinimim": 0.25, 7 | "devmodePayInterval": 120, 8 | 9 | "logips": true, 10 | "anonymizeips": true, 11 | "ipv4bits": 16, 12 | "ipv6bits": 16, 13 | 14 | "poolwarningmsg": "WARNING: POOL IS DEFAULT CONFIGURATION! PLEASE ENSURE TO CHANGE CONFIGURATION!", 15 | 16 | "defaultCoin": "pexacoin", 17 | 18 | "poollogo": "/local/file/path/or_a_web_address.to.png", 19 | 20 | "discordtwitterfacebook": "Join our #mining channel on Discord: https://discord.gg/vzcbVNW", 21 | 22 | "pagetitle": "My Cool Pool - 0% Fees Promo - Run by professionals", 23 | "pageauthor": "My Name Is...", 24 | "pagedesc": "A reliable, low fee, easy to use mining pool for cryptocurrency! No matter your experience with mining cryptocurrency, we make it easy! Get started mining today!", 25 | "pagekeywds": "GPU,CPU,Hash,Hashrate,Cryptocurrency,Crypto,Mining,Pool,Bitcoin,Raven,Ravencoin,Wavi,Wavicoin,Dixicoin,Dixi,QBic,QBicCoin,Easy,Simple,How,To", 26 | 27 | "btcdonations": "18TmiWzbMLyf7MvQMcNWh3hUBVrxBgzrWi", 28 | "ltcdonations": "LX1fUwLVcAaRXvP67ZcqUvjjteaKx1nAvL", 29 | "ethdonations": "0x52fD0B6847E1D3cEc5600359f24d671FdE2Bc65B", 30 | "xmrdonations": "4AVabKt1zGhUckqWC5DKtkcc49ChYFuSETzvDZFfVnUYYY5vC4CSZ9xYzmCWvx7ePYZ9YvxgYbYsLL1L9TfQy2hi5Awsc7d", 31 | 32 | "logger" : { 33 | "level" : "debug", 34 | "file" : "/root/logs/nomp_debug.log" 35 | }, 36 | 37 | "cliHost": "127.0.0.1", 38 | "cliPort": 17117, 39 | 40 | "clustering": { 41 | "enabled": true, 42 | "forks": "auto" 43 | }, 44 | 45 | "defaultPoolConfigs": { 46 | "blockRefreshInterval": 1000, 47 | "jobRebroadcastTimeout": 55, 48 | "connectionTimeout": 600, 49 | "emitInvalidBlockHashes": false, 50 | "validateWorkerUsername": true, 51 | "tcpProxyProtocol": false, 52 | "banning": { 53 | "enabled": true, 54 | "time": 600, 55 | "invalidPercent": 50, 56 | "checkThreshold": 500, 57 | "purgeInterval": 300 58 | }, 59 | "redis": { 60 | "host": "127.0.0.1", 61 | "port": 6379 62 | } 63 | }, 64 | 65 | "website": { 66 | "enabled": true, 67 | "sslenabled": true, 68 | "sslforced": true, 69 | "host": "0.0.0.0", 70 | "port": 80, 71 | "sslport": 443, 72 | "sslkey": "/root/EASYNOMP/certs/privkey.pem", 73 | "sslcert": "/root/EASYNOMP/certs/fullchain.pem", 74 | "stratumHost": "MY_SERVER_IP_OR_DOMAIN_NAME", 75 | "stats": { 76 | "updateInterval": 5, 77 | "historicalRetention": 43200, 78 | "hashrateWindow": 600 79 | }, 80 | "adminCenter": { 81 | "enabled": false, 82 | "password": "NOT_WORKING_YET_:P_LESHACAT_CAN_DO_ADMIN_PANEL_FUNCTIONALITY_TOO" 83 | } 84 | }, 85 | 86 | "redis": { 87 | "host": "127.0.0.1", 88 | "port": 6379 89 | }, 90 | 91 | "switching": { 92 | "switch1": { 93 | "enabled": false, 94 | "algorithm": "sha256", 95 | "ports": { 96 | "3333": { 97 | "diff": 10, 98 | "varDiff": { 99 | "minDiff": 16, 100 | "maxDiff": 512, 101 | "targetTime": 15, 102 | "retargetTime": 90, 103 | "variancePercent": 30 104 | } 105 | } 106 | } 107 | }, 108 | "switch2": { 109 | "enabled": false, 110 | "algorithm": "scrypt", 111 | "ports": { 112 | "4444": { 113 | "diff": 10, 114 | "varDiff": { 115 | "minDiff": 16, 116 | "maxDiff": 512, 117 | "targetTime": 15, 118 | "retargetTime": 90, 119 | "variancePercent": 30 120 | } 121 | } 122 | } 123 | }, 124 | "switch3": { 125 | "enabled": false, 126 | "algorithm": "x11", 127 | "ports": { 128 | "5555": { 129 | "diff": 0.001, 130 | "varDiff": { 131 | "minDiff": 0.001, 132 | "maxDiff": 1, 133 | "targetTime": 15, 134 | "retargetTime": 60, 135 | "variancePercent": 30 136 | } 137 | } 138 | } 139 | } 140 | }, 141 | 142 | "profitSwitch": { 143 | "enabled": false, 144 | "updateInterval": 600, 145 | "depth": 0.90, 146 | "usePoloniex": true, 147 | "useCryptsy": true, 148 | "useMintpal": true, 149 | "useBittrex": true 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /libs/mposCompatibility.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | var cluster = require('cluster'); 3 | 4 | const logger = require('./logger.js').getLogger('MPOSCompatibility', 'system'); 5 | module.exports = function (poolConfig) { 6 | 7 | var mposConfig = poolConfig.mposMode; 8 | var coin = poolConfig.coin.name; 9 | 10 | var connection = mysql.createPool({ 11 | host: mposConfig.host, 12 | port: mposConfig.port, 13 | user: mposConfig.user, 14 | password: mposConfig.password, 15 | database: mposConfig.database 16 | }); 17 | 18 | 19 | var logIdentify = 'MySQL'; 20 | var logComponent = coin; 21 | 22 | 23 | this.handleAuth = function (workerName, password, authCallback) { 24 | 25 | if (poolConfig.validateWorkerUsername !== true && mposConfig.autoCreateWorker !== true) { 26 | authCallback(true); 27 | return; 28 | } 29 | 30 | connection.query( 31 | 'SELECT password FROM pool_worker WHERE username = LOWER(?)', 32 | [workerName.toLowerCase()], 33 | function (err, result) { 34 | if (err) { 35 | logger.error('Database error when authenticating worker, err = %s', JSON.stringify(err)); 36 | authCallback(false); 37 | } 38 | else if (!result[0]) { 39 | if (mposConfig.autoCreateWorker) { 40 | var account = workerName.split('.')[0]; 41 | connection.query( 42 | 'SELECT id,username FROM accounts WHERE username = LOWER(?)', 43 | [account.toLowerCase()], 44 | function (err, result) { 45 | if (err) { 46 | logger.error('Database error when authenticating account, err = %s', JSON.stringify(err)); 47 | authCallback(false); 48 | } else if (!result[0]) { 49 | authCallback(false); 50 | } else { 51 | connection.query( 52 | "INSERT INTO `pool_worker` (`account_id`, `username`, `password`) VALUES (?, ?, ?);", 53 | [result[0].id, workerName.toLowerCase(), password], 54 | function (err, result) { 55 | if (err) { 56 | logger.error('Database error when insert worker, err = %s', JSON.stringify(err)); 57 | authCallback(false); 58 | } else { 59 | authCallback(true); 60 | } 61 | }) 62 | } 63 | } 64 | ); 65 | } 66 | else { 67 | authCallback(false); 68 | } 69 | } 70 | else if (mposConfig.checkPassword && result[0].password !== password) { 71 | authCallback(false); 72 | } 73 | else { 74 | authCallback(true); 75 | } 76 | } 77 | ); 78 | 79 | }; 80 | 81 | this.handleShare = function (isValidShare, isValidBlock, shareData) { 82 | 83 | var dbData = [ 84 | shareData.ip, 85 | shareData.worker, 86 | isValidShare ? 'Y' : 'N', 87 | isValidBlock ? 'Y' : 'N', 88 | shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1), 89 | typeof(shareData.error) === 'undefined' ? null : shareData.error, 90 | shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '') 91 | ]; 92 | connection.query( 93 | 'INSERT INTO `shares` SET time = NOW(), rem_host = ?, username = ?, our_result = ?, upstream_result = ?, difficulty = ?, reason = ?, solution = ?', 94 | dbData, 95 | function (err, result) { 96 | if (err) { 97 | logger.error('Insert error when adding share, err = %s', JSON.stringify(err)); 98 | } 99 | else { 100 | logger.debug('Share inserted'); 101 | } 102 | } 103 | ); 104 | }; 105 | 106 | this.handleDifficultyUpdate = function (workerName, diff) { 107 | 108 | connection.query( 109 | 'UPDATE `pool_worker` SET `difficulty` = ' + diff + ' WHERE `username` = ' + connection.escape(workerName), 110 | function (err, result) { 111 | if (err) { 112 | logger.error('Error when updating worker diff, err = %s', JSON.stringify(err)); 113 | } 114 | else if (result.affectedRows === 0) { 115 | connection.query('INSERT INTO `pool_worker` SET ?', {username: workerName, difficulty: diff}); 116 | } 117 | else { 118 | logger.debug('Updated difficulty successfully, result = %s', result); 119 | } 120 | } 121 | ); 122 | }; 123 | 124 | 125 | }; 126 | -------------------------------------------------------------------------------- /website/pages/history.html: -------------------------------------------------------------------------------- 1 | 39 |
    40 | {{ var sortedPools = Object.keys(it.stats.pools); }} 41 | {{ sortedPools.sort(function(a, b) { return a.workerCount - b.workerCount; }); }} 42 |
    43 | 44 | 45 | 46 | 47 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
    Block History For 48 | 59 |
    {{=sortedPools[0].charAt(0).toUpperCase() + sortedPools[0].slice(1)}} History
    Pending Blocks:{{=it.stats.pools[sortedPools[0]].blocks.pending}}
    Confirmed Blocks:{{=it.stats.pools[sortedPools[0]].blocks.confirmed}}
    76 |
    77 |

    Last 50 Confirmed Blocks

    78 |
    79 |
    80 |
    81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
    BlockHashDifficultyConfirmations
    93 |
    94 |
    95 |
    96 |
    97 | 98 | 147 | -------------------------------------------------------------------------------- /website/static/stats.js: -------------------------------------------------------------------------------- 1 | var poolWorkerData; 2 | var poolHashrateData; 3 | var poolBlockData; 4 | 5 | var poolWorkerChart; 6 | var poolHashrateChart; 7 | var poolBlockChart; 8 | 9 | var statData; 10 | var poolKeys; 11 | 12 | function buildChartData() { 13 | 14 | var pools = {}; 15 | 16 | poolKeys = []; 17 | for (var i = 0; i < statData.length; i++) { 18 | for (var pool in statData[i].pools) { 19 | if (poolKeys.indexOf(pool) === -1) 20 | poolKeys.push(pool); 21 | } 22 | } 23 | 24 | 25 | for (var i = 0; i < statData.length; i++) { 26 | var time = statData[i].time * 1000; 27 | for (var f = 0; f < poolKeys.length; f++) { 28 | var pName = poolKeys[f]; 29 | var a = pools[pName] = (pools[pName] || { 30 | hashrate: [], 31 | workers: [], 32 | blocks: [] 33 | }); 34 | if (pName in statData[i].pools) { 35 | a.hashrate.push([time, statData[i].pools[pName].hashrate]); 36 | a.workers.push([time, statData[i].pools[pName].workerCount]); 37 | a.blocks.push({ t: new Date(time), y: statData[i].pools[pName].blocks.pending}) 38 | } else { 39 | a.hashrate.push([time, 0]); 40 | a.workers.push([time, 0]); 41 | a.blocks.push({x: time, y: 0}) 42 | } 43 | } 44 | } 45 | 46 | poolWorkerData = []; 47 | poolHashrateData = []; 48 | poolBlockData = []; 49 | 50 | for (var pool in pools) { 51 | poolWorkerData.push({ 52 | label: pool, 53 | value: pools[pool].workers[pools[pool].workers.length - 1][1] 54 | }); 55 | poolHashrateData.push({ 56 | label: pool, 57 | value: parseInt(pools[pool].hashrate[pools[pool].hashrate.length - 1][1] / 2048) 58 | }); 59 | poolBlockData.push({ 60 | label: pool, 61 | value: pools[pool].blocks 62 | }); 63 | } 64 | } 65 | 66 | function getReadableHashRateString(hashrate) { 67 | var i = -1; 68 | var byteUnits = [' KH', ' MH', ' GH', ' TH', ' PH']; 69 | do { 70 | hashrate = hashrate / 1024; 71 | i++; 72 | } while (hashrate > 1024); 73 | return Math.round(hashrate) + byteUnits[i]; 74 | } 75 | 76 | function timeOfDayFormat(timestamp) { 77 | var dStr = d3.time.format('%I:%M %p')(new Date(timestamp)); 78 | if (dStr.indexOf('0') === 0) dStr = dStr.slice(1); 79 | return dStr; 80 | } 81 | 82 | function displayCharts() { 83 | var chartColors = [ 84 | '#1976D2', 85 | '#388E3C', 86 | '#FBC02D', 87 | '#512DA8', 88 | '#C2185B' 89 | ]; 90 | poolWorkerChart = new Chart($("#workerChart"), { 91 | type: 'pie', 92 | data: { 93 | labels: poolWorkerData.slice(0, 5).map(x => x.label), 94 | datasets: [{ 95 | data: poolWorkerData.slice(0, 5).map(x => x.value), 96 | backgroundColor: chartColors 97 | }], 98 | }, 99 | options: { 100 | responsive: true 101 | } 102 | }); 103 | 104 | poolHashrateChart = new Chart($("#hashChart"), { 105 | type: 'pie', 106 | data: { 107 | labels: poolHashrateData.slice(0, 5).map(x => x.label), 108 | datasets: [{ 109 | data: poolHashrateData.slice(0, 5).map(x => x.value), 110 | backgroundColor: chartColors 111 | }] 112 | }, 113 | options: { 114 | responsive: true 115 | } 116 | }); 117 | 118 | var blockData = []; 119 | var labels = poolBlockData.slice(0, 5).map(x => x.label); 120 | var values = poolBlockData.slice(0, 5).map(x => x.value); 121 | for(var i = 0; i < poolBlockData.length; i++) { 122 | blockData.push( 123 | { 124 | label: labels[i], 125 | data: values[i], 126 | backgroundColor: chartColors[i], 127 | borderColor: chartColors[i] 128 | } 129 | ); 130 | } 131 | $("#blockChart").height = 200; 132 | poolBlockChart = new Chart($("#blockChart"), { 133 | type: 'line', 134 | data: { 135 | datasets: blockData 136 | }, 137 | options: { 138 | maintainAspectRatio: false, 139 | responsive: true, 140 | scales: { 141 | xAxes: [{ 142 | time: { 143 | unit: 'minute' 144 | } 145 | }] 146 | } 147 | } 148 | }); 149 | 150 | } 151 | 152 | function pastelColors() { 153 | var r = (Math.round(Math.random() * 127) + 127).toString(16); 154 | var g = (Math.round(Math.random() * 127) + 127).toString(16); 155 | var b = (Math.round(Math.random() * 127) + 127).toString(16); 156 | return '#' + r + g + b; 157 | } 158 | 159 | function TriggerChartUpdates() { 160 | poolWorkerChart.update(); 161 | poolHashrateChart.update(); 162 | poolBlockChart.update(); 163 | } 164 | 165 | $.getJSON('/api/pool_stats', function(data) { 166 | statData = data; 167 | buildChartData(); 168 | displayCharts(); 169 | }); 170 | 171 | 172 | 173 | statsSource.addEventListener('message', function(e) { 174 | var stats = JSON.parse(e.data); 175 | statData.push(stats); 176 | 177 | var newPoolAdded = (function() { 178 | for (var p in stats.pools) { 179 | if (poolKeys.indexOf(p) === -1) 180 | return true; 181 | } 182 | return false; 183 | })(); 184 | 185 | if (newPoolAdded || Object.keys(stats.pools).length > poolKeys.length) { 186 | buildChartData(); 187 | displayCharts(); 188 | } else { 189 | var time = stats.time * 1000; 190 | for (var f = 0; f < poolKeys.length; f++) { 191 | var pool = poolKeys[f]; 192 | for (var i = 0; i < poolWorkerData.length; i++) { 193 | if (poolWorkerData[i].key === pool) { 194 | poolWorkerData[i].values.shift(); 195 | poolWorkerData[i].values.push([time, pool in stats.pools ? stats.pools[pool].workerCount : 0]); 196 | break; 197 | } 198 | } 199 | for (var i = 0; i < poolHashrateData.length; i++) { 200 | if (poolHashrateData[i].key === pool) { 201 | poolHashrateData[i].values.shift(); 202 | poolHashrateData[i].values.push([time, pool in stats.pools ? stats.pools[pool].hashrate : 0]); 203 | break; 204 | } 205 | } 206 | for (var i = 0; i < poolBlockData.length; i++) { 207 | if (poolBlockData[i].key === pool) { 208 | poolBlockData[i].values.shift(); 209 | poolBlockData[i].values.push([time, pool in stats.pools ? stats.pools[pool].blocks.pending : 0]); 210 | break; 211 | } 212 | } 213 | } 214 | TriggerChartUpdates(); 215 | } 216 | 217 | }); 218 | -------------------------------------------------------------------------------- /website/pages/pool_stats.html: -------------------------------------------------------------------------------- 1 | 13 | 14 |
    15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {{ for(var pool in it.stats.pools) { }} 27 | {{ if(pool !== it.stats.coin) continue; }} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {{ for(var pool in it.poolsConfigs) { }} 48 | {{ if(pool !== it.stats.coin) continue; }} 49 | 50 | {{ break; }} 51 | {{ } }} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 61 | 62 | {{ var blocktime = it.stats.pools[pool].blockTime || 0; }} 63 | {{ var blockchange = it.stats.pools[pool].blockChange || 0; }} 64 | {{ var blockheight = it.stats.pools[pool].poolStats.networkBlocks || 0; }} 65 | 66 | {{ var changehours = Number.parseFloat((((blockchange) - (blockheight % blockchange)) / blocktime)).toFixed(2) || 0; }} 67 | {{ var changeblocks = ((blockchange) - (blockheight % blockchange)) || 0; }} 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {{ break; }} 79 | {{ } }} 80 | 81 |
    General InformationPool InformationNetwork Information
    Coin: icon {{=(String(it.stats.coin).charAt(0).toUpperCase() + String(it.stats.coin).slice(1))}}Workers:{{=it.stats.pools[pool].workerCount}}Hashrate:{{=it.stats.pools[pool].hashrateString}}Pending Blocks:{{=it.stats.pools[pool].blocks.pending}}Hashrate:{{=it.stats.pools[pool].poolStats.networkSolsString}}Height:{{=it.stats.pools[pool].poolStats.networkBlocks}}
    Ports:{{=Object.keys(it.poolsConfigs[pool].ports)}}Valid Shares:{{=it.stats.pools[pool].poolStats.validShares}}Mature Blocks:{{=it.stats.pools[pool].blocks.confirmed}}Difficulty:{{=Number(Math.round(it.stats.pools[pool].poolStats.networkDiff + 'e' + 4) + 'e-' + 4)}}Diff change in: {{=changeblocks}} blocks (appx. {{=changehours}} hours)
    {{=it.stats.pools[pool].name.charAt(0).toUpperCase() + it.stats.pools[pool].name.slice(1)}} Donations: {{=(it.poolsConfigs[pool].donateaddress||'none')}}
    82 |
    83 |
    84 | 85 |
     
    86 | 87 |
    88 |
    89 |
    Hashrate History
    90 |
    91 |
    92 |
    93 | 94 |
     
    95 | 96 |
    97 |
    98 |
    Pending Block History
    99 |
    100 |
    101 |
    102 | 103 |
     
    104 | 105 |
    106 |
    107 |
    Worker History
    108 |
    109 |
    110 |
    111 | 112 |
    113 | 114 | 115 | 116 | 126 | -------------------------------------------------------------------------------- /libs/apiCryptsy.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php', 12 | PRIVATE_API_URL = 'https://api.cryptsy.com/api', 13 | USER_AGENT = 'nomp/node-open-mining-portal' 14 | 15 | // Constructor 16 | function Cryptsy(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Cryptsy: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Cryptsy.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '_' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Cryptsy.prototype = { 50 | constructor: Cryptsy, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Cryptsy.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var parameters = { 103 | method: 'marketdatav2' 104 | }; 105 | 106 | return this._public(parameters, callback); 107 | }, 108 | 109 | getOrderBook: function(currencyA, currencyB, callback){ 110 | var parameters = { 111 | command: 'returnOrderBook', 112 | currencyPair: joinCurrencies(currencyA, currencyB) 113 | }; 114 | 115 | return this._public(parameters, callback); 116 | }, 117 | 118 | getTradeHistory: function(currencyA, currencyB, callback){ 119 | var parameters = { 120 | command: 'returnTradeHistory', 121 | currencyPair: joinCurrencies(currencyA, currencyB) 122 | }; 123 | 124 | return this._public(parameters, callback); 125 | }, 126 | 127 | 128 | ///// 129 | 130 | 131 | // PRIVATE METHODS 132 | 133 | myBalances: function(callback){ 134 | var parameters = { 135 | command: 'returnBalances' 136 | }; 137 | 138 | return this._private(parameters, callback); 139 | }, 140 | 141 | myOpenOrders: function(currencyA, currencyB, callback){ 142 | var parameters = { 143 | command: 'returnOpenOrders', 144 | currencyPair: joinCurrencies(currencyA, currencyB) 145 | }; 146 | 147 | return this._private(parameters, callback); 148 | }, 149 | 150 | myTradeHistory: function(currencyA, currencyB, callback){ 151 | var parameters = { 152 | command: 'returnTradeHistory', 153 | currencyPair: joinCurrencies(currencyA, currencyB) 154 | }; 155 | 156 | return this._private(parameters, callback); 157 | }, 158 | 159 | buy: function(currencyA, currencyB, rate, amount, callback){ 160 | var parameters = { 161 | command: 'buy', 162 | currencyPair: joinCurrencies(currencyA, currencyB), 163 | rate: rate, 164 | amount: amount 165 | }; 166 | 167 | return this._private(parameters, callback); 168 | }, 169 | 170 | sell: function(currencyA, currencyB, rate, amount, callback){ 171 | var parameters = { 172 | command: 'sell', 173 | currencyPair: joinCurrencies(currencyA, currencyB), 174 | rate: rate, 175 | amount: amount 176 | }; 177 | 178 | return this._private(parameters, callback); 179 | }, 180 | 181 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 182 | var parameters = { 183 | command: 'cancelOrder', 184 | currencyPair: joinCurrencies(currencyA, currencyB), 185 | orderNumber: orderNumber 186 | }; 187 | 188 | return this._private(parameters, callback); 189 | }, 190 | 191 | withdraw: function(currency, amount, address, callback){ 192 | var parameters = { 193 | command: 'withdraw', 194 | currency: currency, 195 | amount: amount, 196 | address: address 197 | }; 198 | 199 | return this._private(parameters, callback); 200 | } 201 | }; 202 | 203 | return Cryptsy; 204 | }(); 205 | -------------------------------------------------------------------------------- /libs/apiPoloniex.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'https://poloniex.com/public', 12 | PRIVATE_API_URL = 'https://poloniex.com/tradingApi', 13 | USER_AGENT = 'npm-crypto-apis/' + version 14 | 15 | // Constructor 16 | function Poloniex(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Poloniex: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Poloniex.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '_' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Poloniex.prototype = { 50 | constructor: Poloniex, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Poloniex.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var parameters = { 103 | command: 'returnTicker' 104 | }; 105 | 106 | return this._public(parameters, callback); 107 | }, 108 | 109 | get24hVolume: function(callback){ 110 | var parameters = { 111 | command: 'return24hVolume' 112 | }; 113 | 114 | return this._public(parameters, callback); 115 | }, 116 | 117 | getOrderBook: function(currencyA, currencyB, callback){ 118 | var parameters = { 119 | command: 'returnOrderBook', 120 | currencyPair: joinCurrencies(currencyA, currencyB) 121 | }; 122 | 123 | return this._public(parameters, callback); 124 | }, 125 | 126 | getTradeHistory: function(currencyA, currencyB, callback){ 127 | var parameters = { 128 | command: 'returnTradeHistory', 129 | currencyPair: joinCurrencies(currencyA, currencyB) 130 | }; 131 | 132 | return this._public(parameters, callback); 133 | }, 134 | 135 | 136 | ///// 137 | 138 | 139 | // PRIVATE METHODS 140 | 141 | myBalances: function(callback){ 142 | var parameters = { 143 | command: 'returnBalances' 144 | }; 145 | 146 | return this._private(parameters, callback); 147 | }, 148 | 149 | myOpenOrders: function(currencyA, currencyB, callback){ 150 | var parameters = { 151 | command: 'returnOpenOrders', 152 | currencyPair: joinCurrencies(currencyA, currencyB) 153 | }; 154 | 155 | return this._private(parameters, callback); 156 | }, 157 | 158 | myTradeHistory: function(currencyA, currencyB, callback){ 159 | var parameters = { 160 | command: 'returnTradeHistory', 161 | currencyPair: joinCurrencies(currencyA, currencyB) 162 | }; 163 | 164 | return this._private(parameters, callback); 165 | }, 166 | 167 | buy: function(currencyA, currencyB, rate, amount, callback){ 168 | var parameters = { 169 | command: 'buy', 170 | currencyPair: joinCurrencies(currencyA, currencyB), 171 | rate: rate, 172 | amount: amount 173 | }; 174 | 175 | return this._private(parameters, callback); 176 | }, 177 | 178 | sell: function(currencyA, currencyB, rate, amount, callback){ 179 | var parameters = { 180 | command: 'sell', 181 | currencyPair: joinCurrencies(currencyA, currencyB), 182 | rate: rate, 183 | amount: amount 184 | }; 185 | 186 | return this._private(parameters, callback); 187 | }, 188 | 189 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 190 | var parameters = { 191 | command: 'cancelOrder', 192 | currencyPair: joinCurrencies(currencyA, currencyB), 193 | orderNumber: orderNumber 194 | }; 195 | 196 | return this._private(parameters, callback); 197 | }, 198 | 199 | withdraw: function(currency, amount, address, callback){ 200 | var parameters = { 201 | command: 'withdraw', 202 | currency: currency, 203 | amount: amount, 204 | address: address 205 | }; 206 | 207 | return this._private(parameters, callback); 208 | } 209 | }; 210 | 211 | return Poloniex; 212 | }(); 213 | -------------------------------------------------------------------------------- /website/pages/learn_more.html: -------------------------------------------------------------------------------- 1 | 28 | 29 |
    30 |
    31 |
    32 |

    Cryptocurrency Mining

    33 |

    You may have heard about Cryptocurrency or Bitcoin mining on the news, through a friend, or even just on a random search. No matter how you heard about it, you may be asking yourself what exactly is it? Is it something that I could start 34 | doing at home? Is it even worth my time? This page will give you a better understanding of what cryptocurrency mining is, and even show you how to get started!

    35 |
    36 |
    37 |
    38 |
    39 |

    So, What Is It?

    40 |
    41 |

    "Cryptocurrency mining is the process by which transactions are verified and added to the public ledger, known as the block chain, and the means through which new coins are released.  Anyone with access to the internet 42 | and suitable hardware can participate in mining.  The mining process involves compiling recent transactions into blocks and trying to solve a computationally difficult puzzle.  The participant who first solves the 43 | puzzle gets to place the next block on the block chain and claim the rewards.  The rewards, which incentivize mining, are both the transaction fees associated with the transactions compiled in the block as well as 44 | newly released coins."

    45 | 46 |
    47 |
    48 |
    49 |
    50 |
    51 |

    Can I Mine with My Computer?

    52 |

    Anyone can mine; the only limiting factor being the hardware which you wish to mine from. If you have a GPU (Graphics Processing Unit) or a compatable CPU (Central Processing Unit), and mining software which supports your GPU/CPU as well as the algorithm of the coin you are trying to mine then 53 | you should have no issues! Don't know what mining software or algorithms are? Don't worry, we will cover that shortly!

    54 |
    55 |
    56 |
    57 |
    58 |

    What Is Mining Software?

    59 |

    Mining software, are applications, usually command line, that allows users to connect to the block chain or pool, receive work, and use hardware to solve work given. There are two very popular choices when it comes to software, CCMiner, Suprminer, TRex 60 | for Nvidia GPU's, Wildrig, SGMiner for AMD GPU's, CPUMINER-MULTI, CPUMINER-OPT for CPU's. A quick search online will give you more information about these programs, though look carefully, there could be malicious content; for safety we recommend find the source code for the 61 | project before downloading.

    62 |
    63 |
    64 |
    65 |
    66 |

    What Is an Algorithm?

    67 |

    An algorithm is defined as "a process or set of rules to be followed in calculations or other problem-solving operations, especially by a computer".  Cryptocurrencies miners use cryptographic algorithms such as SHA-2, Scrypt, 68 | and Equihash to generate hashes for the block chain. Each algorithm has positive and negatives for each given hardware setup, so researching your hardware prior to chosing a coin to mine, can make your venture much more profitable.

    69 |
    70 |
    71 |
    72 |
    73 |

    How Do I Start?

    74 |
      75 |
    • Well firstly, you need to pick a mining software which is compatable with your system. While I will not be providing links to any mining software, if you refer back to popular choices I mentioned above, a quick online search will 76 | show you the software needed.
    • 77 |
    • Pick a coin you wish to mine. Remember each coin has an algorithm which needs to be accounted for, as you will need to verify that your mining software supports it. There is a README file usually included with most mining software, 78 | and inside you can see each supported algorithm, as well as other useful options.
    • 79 |
    • After you decided which coin, you need to get a wallet. Each coin provides it's own wallet software which you can download from the coin's website or project repository. Download the wallet, open it, let it sync (this could take 80 | a while), once it's syncs, find your wallet address and hold it for the next step.
    • 81 |
    • Inside of the folder of your mining software application, create a .bat or .cmd file. Edit the file and use our configuration generator on the Getting Started page to generate a personalized configuration, copy and paste the results 82 | into your file. Save it, and you should now be able to double click the file to start the miner. Good luck mining!
    • 83 |
    84 |
    85 |
    86 |
    87 |
    88 |

    Having Trouble?

    89 |

    {{=it.portalConfig.discordtwitterfacebook}}

    90 |
    91 |
    92 |
    93 | 96 | -------------------------------------------------------------------------------- /libs/apiMintpal.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'https://api.mintpal.com/v2/market', 12 | PRIVATE_API_URL = 'https://api.mintpal.com/v2/market', 13 | USER_AGENT = 'nomp/node-open-mining-portal' 14 | 15 | // Constructor 16 | function Mintpal(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Mintpal: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Mintpal.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '_' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Mintpal.prototype = { 50 | constructor: Mintpal, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Mintpal.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var options = { 103 | method: 'GET', 104 | url: PUBLIC_API_URL + '/summary', 105 | qs: null 106 | }; 107 | 108 | return this._request(options, callback); 109 | }, 110 | 111 | getBuyOrderBook: function(currencyA, currencyB, callback){ 112 | var options = { 113 | method: 'GET', 114 | url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY', 115 | qs: null 116 | }; 117 | 118 | return this._request(options, callback); 119 | }, 120 | 121 | getOrderBook: function(currencyA, currencyB, callback){ 122 | var parameters = { 123 | command: 'returnOrderBook', 124 | currencyPair: joinCurrencies(currencyA, currencyB) 125 | }; 126 | 127 | return this._public(parameters, callback); 128 | }, 129 | 130 | getTradeHistory: function(currencyA, currencyB, callback){ 131 | var parameters = { 132 | command: 'returnTradeHistory', 133 | currencyPair: joinCurrencies(currencyA, currencyB) 134 | }; 135 | 136 | return this._public(parameters, callback); 137 | }, 138 | 139 | 140 | ///// 141 | 142 | 143 | // PRIVATE METHODS 144 | 145 | myBalances: function(callback){ 146 | var parameters = { 147 | command: 'returnBalances' 148 | }; 149 | 150 | return this._private(parameters, callback); 151 | }, 152 | 153 | myOpenOrders: function(currencyA, currencyB, callback){ 154 | var parameters = { 155 | command: 'returnOpenOrders', 156 | currencyPair: joinCurrencies(currencyA, currencyB) 157 | }; 158 | 159 | return this._private(parameters, callback); 160 | }, 161 | 162 | myTradeHistory: function(currencyA, currencyB, callback){ 163 | var parameters = { 164 | command: 'returnTradeHistory', 165 | currencyPair: joinCurrencies(currencyA, currencyB) 166 | }; 167 | 168 | return this._private(parameters, callback); 169 | }, 170 | 171 | buy: function(currencyA, currencyB, rate, amount, callback){ 172 | var parameters = { 173 | command: 'buy', 174 | currencyPair: joinCurrencies(currencyA, currencyB), 175 | rate: rate, 176 | amount: amount 177 | }; 178 | 179 | return this._private(parameters, callback); 180 | }, 181 | 182 | sell: function(currencyA, currencyB, rate, amount, callback){ 183 | var parameters = { 184 | command: 'sell', 185 | currencyPair: joinCurrencies(currencyA, currencyB), 186 | rate: rate, 187 | amount: amount 188 | }; 189 | 190 | return this._private(parameters, callback); 191 | }, 192 | 193 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 194 | var parameters = { 195 | command: 'cancelOrder', 196 | currencyPair: joinCurrencies(currencyA, currencyB), 197 | orderNumber: orderNumber 198 | }; 199 | 200 | return this._private(parameters, callback); 201 | }, 202 | 203 | withdraw: function(currency, amount, address, callback){ 204 | var parameters = { 205 | command: 'withdraw', 206 | currency: currency, 207 | amount: amount, 208 | address: address 209 | }; 210 | 211 | return this._private(parameters, callback); 212 | } 213 | }; 214 | 215 | return Mintpal; 216 | }(); 217 | -------------------------------------------------------------------------------- /libs/apiBittrex.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'https://bittrex.com/api/v1/public', 12 | PRIVATE_API_URL = 'https://bittrex.com/api/v1/market', 13 | USER_AGENT = 'nomp/node-open-mining-portal' 14 | 15 | // Constructor 16 | function Bittrex(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Bittrex: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Bittrex.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '-' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Bittrex.prototype = { 50 | constructor: Bittrex, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Bittrex.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var options = { 103 | method: 'GET', 104 | url: PUBLIC_API_URL + '/getmarketsummaries', 105 | qs: null 106 | }; 107 | 108 | return this._request(options, callback); 109 | }, 110 | 111 | // getBuyOrderBook: function(currencyA, currencyB, callback){ 112 | // var options = { 113 | // method: 'GET', 114 | // url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY', 115 | // qs: null 116 | // }; 117 | 118 | // return this._request(options, callback); 119 | // }, 120 | 121 | getOrderBook: function(currencyA, currencyB, callback){ 122 | var parameters = { 123 | market: joinCurrencies(currencyA, currencyB), 124 | type: 'buy', 125 | depth: '50' 126 | } 127 | var options = { 128 | method: 'GET', 129 | url: PUBLIC_API_URL + '/getorderbook', 130 | qs: parameters 131 | } 132 | 133 | return this._request(options, callback); 134 | }, 135 | 136 | getTradeHistory: function(currencyA, currencyB, callback){ 137 | var parameters = { 138 | command: 'returnTradeHistory', 139 | currencyPair: joinCurrencies(currencyA, currencyB) 140 | }; 141 | 142 | return this._public(parameters, callback); 143 | }, 144 | 145 | 146 | ///// 147 | 148 | 149 | // PRIVATE METHODS 150 | 151 | myBalances: function(callback){ 152 | var parameters = { 153 | command: 'returnBalances' 154 | }; 155 | 156 | return this._private(parameters, callback); 157 | }, 158 | 159 | myOpenOrders: function(currencyA, currencyB, callback){ 160 | var parameters = { 161 | command: 'returnOpenOrders', 162 | currencyPair: joinCurrencies(currencyA, currencyB) 163 | }; 164 | 165 | return this._private(parameters, callback); 166 | }, 167 | 168 | myTradeHistory: function(currencyA, currencyB, callback){ 169 | var parameters = { 170 | command: 'returnTradeHistory', 171 | currencyPair: joinCurrencies(currencyA, currencyB) 172 | }; 173 | 174 | return this._private(parameters, callback); 175 | }, 176 | 177 | buy: function(currencyA, currencyB, rate, amount, callback){ 178 | var parameters = { 179 | command: 'buy', 180 | currencyPair: joinCurrencies(currencyA, currencyB), 181 | rate: rate, 182 | amount: amount 183 | }; 184 | 185 | return this._private(parameters, callback); 186 | }, 187 | 188 | sell: function(currencyA, currencyB, rate, amount, callback){ 189 | var parameters = { 190 | command: 'sell', 191 | currencyPair: joinCurrencies(currencyA, currencyB), 192 | rate: rate, 193 | amount: amount 194 | }; 195 | 196 | return this._private(parameters, callback); 197 | }, 198 | 199 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 200 | var parameters = { 201 | command: 'cancelOrder', 202 | currencyPair: joinCurrencies(currencyA, currencyB), 203 | orderNumber: orderNumber 204 | }; 205 | 206 | return this._private(parameters, callback); 207 | }, 208 | 209 | withdraw: function(currency, amount, address, callback){ 210 | var parameters = { 211 | command: 'withdraw', 212 | currency: currency, 213 | amount: amount, 214 | address: address 215 | }; 216 | 217 | return this._private(parameters, callback); 218 | } 219 | }; 220 | 221 | return Bittrex; 222 | }(); 223 | -------------------------------------------------------------------------------- /website/static/stat_tracker.js: -------------------------------------------------------------------------------- 1 | //The stat object to hold everything we are tracking 2 | var stats = {}; 3 | 4 | //Gets the desired pool stats stored in our cache 5 | var getPoolStats = function(key) { 6 | return stats['p_' + key]; 7 | } 8 | 9 | //Gets the desired worker stats stored in our cache 10 | var getWorkerStats = function(address) { 11 | return stats['w_' + address]; 12 | } 13 | 14 | //Adds a worker to the stat tracker 15 | var addWorkerToTracker = function(statData, workerData, address, callback) { 16 | if (stats['w_' + address]) { 17 | updateWorkerData(statData, workerData, address, callback); 18 | } else { 19 | buildWorkerData(statData, workerData, address, callback); 20 | } 21 | } 22 | 23 | //Adds a pool to the stat tracker 24 | var addPoolToTracker = function(poolData, poolName, callback) { 25 | if (stats['p_' + poolName]) { 26 | updatePoolData(poolData, poolName, callback); 27 | } else { 28 | buildPoolData(poolData, poolName, callback); 29 | } 30 | } 31 | 32 | /* 33 | Updates the stat cache at the given key. 34 | @param key the stat key to update 35 | @param value the value to update stat with 36 | @param index the index in our stat object to set value for 37 | */ 38 | var update = function(key, value, index = 0) { 39 | var stats = stats[key]; 40 | if (stats) { 41 | var statsValues = stats.values[index]; 42 | if (statsValues) { 43 | statsValues.shift(); 44 | statsValues.push(value); 45 | } 46 | } 47 | } 48 | 49 | //builds the initial stat data object for a worker 50 | var buildWorkerData = function(statData, workerData, address, callback = null) { 51 | if (!address || !workerData) { 52 | return; 53 | } 54 | var account = { 55 | paid: workerData.paid, 56 | balance: workerData.balances, 57 | hashrate: 0, 58 | poolHashrate: 0, 59 | shares: workerData.totalShares, 60 | currRoundShares: 0, 61 | symbol: '', 62 | pool: '', 63 | poolSize: 0, 64 | currRoundPoolShares: 0, 65 | invalidShares: 0, 66 | miners: {} 67 | }; 68 | $.getJSON('/api/stats', function(data) { 69 | for (var p in data.pools) { 70 | for (var w in data.pools[p].workers) { 71 | var worker = getWorkerNameFromAddress(w); 72 | if (w.split(".")[0] === _miner) { 73 | var a = account.miners[w] = (account.miners[worker] || { 74 | key: worker, 75 | paid: data.pools[p].workers[w].paid, 76 | balance: data.pools[p].workers[w].paid, 77 | hashrate: [], 78 | validShares: data.pools[p].workers[w].shares, 79 | currRoundShares: data.pools[p].workers[w].currRoundShares, 80 | invalidShares: data.pools[p].workers[w].invalidshares 81 | }); 82 | account.invalidShares += data.pools[p].workers[w].invalidshares; 83 | account.currRoundShares += data.pools[p].workers[w].currRoundShares; 84 | account.hashrate += data.pools[p].workers[w].hashrate; 85 | if (account.symbol.length < 1) { 86 | account.symbol = data.pools[p].symbol; 87 | account.poolSize = data.pools[p].workers ? Object.keys(data.pools[p].workers).length : 0; 88 | account.pool = p; 89 | } 90 | } 91 | } 92 | } 93 | if(data.pools[account.pool] && data.pools[account.pool].workers){ 94 | for (var w in data.pools[account.pool].workers) { 95 | account.poolHashrate += data.pools[account.pool].workers[w].hashrate; 96 | account.currRoundPoolShares += data.pools[account.pool].workers[w].currRoundShares; 97 | } 98 | } 99 | for (var w in workerData.history) { 100 | var worker = getWorkerNameFromAddress(w); 101 | var a = account.miners[w] = (account.miners[worker] || { 102 | key: worker, 103 | paid: 0, 104 | balance: 0, 105 | hashrate: [], 106 | validShares: 0, 107 | currRoundShares: 0, 108 | invalidShares: 0 109 | }); 110 | for (var wh in workerData.history[w]) { 111 | a.hashrate.push([workerData.history[w][wh].time * 1000, workerData.history[w][wh].hashrate]); 112 | } 113 | } 114 | var key = 'w_' + address; 115 | stats[key] = account; 116 | if (callback != null) { 117 | callback(); 118 | } 119 | }); 120 | } 121 | 122 | //builds the initial stat data object for a pool 123 | var buildPoolData = function(statData, poolName, callback = null) { 124 | if (!poolName || !statData) { 125 | return; 126 | } 127 | $.getJSON('/api/pool_stats', function(data) { 128 | var pool = { 129 | hashrate: [], 130 | averagedHashrate: [], 131 | workers: [], 132 | averagedWorkers: [], 133 | blocks: [] 134 | }; 135 | var totalHashrate = 0; 136 | var totalWorkers = 0; 137 | var count = 0; 138 | for (var i = 0; i < statData.length; i++) { 139 | var time = statData[i].time * 1000; 140 | if (!statData[i].pools) { 141 | continue; 142 | } 143 | if (poolName in statData[i].pools) { 144 | var hash = statData[i].pools[poolName].hashrate; 145 | var workers = statData[i].pools[poolName].workerCount; 146 | totalHashrate += hash; 147 | totalWorkers += workers; 148 | count++; 149 | var averaged = (totalHashrate > 0 && count > 1) ? totalHashrate / count : hash; 150 | var averagedWorkers = (totalWorkers > 0 && count > 1) ? totalWorkers / count : workers; 151 | pool.hashrate.push([time, hash]); 152 | pool.averagedHashrate.push([time, averaged]); 153 | pool.workers.push([time, workers]); 154 | pool.averagedWorkers.push([time, averagedWorkers]); 155 | pool.blocks.push([time, statData[i].pools[poolName].blocks.pending]) 156 | } else { 157 | pool.hashrate.push([time, 0]); 158 | pool.workers.push([time, 0]); 159 | pool.averagedWorkers.push([time, 0]); 160 | pool.blocks.push([time, 0]) 161 | } 162 | } 163 | var key = 'p_' + poolName; 164 | stats[key] = pool; 165 | if (callback != null) { 166 | callback(); 167 | } 168 | }); 169 | } 170 | 171 | //updates stat data objects for pools stored within the cache 172 | var updatePoolData = function(statData, poolName, callback = null) { 173 | var pool = stats['p_' + poolName]; 174 | if (pool) { 175 | var time = statData.time * 1000; 176 | if (poolName in statData.pools) { 177 | var hash = statData.pools[poolName].hashrate; 178 | pool.hashrate.push([time, hash]); 179 | pool.averagedHashrate.push([time, pool.hashrate.reduce(function(a, b) { 180 | return a[1] + b[1]; 181 | }) / pool.hashrate.length]); 182 | pool.workers.push([time, statData.pools[poolName].workerCount]); 183 | pool.blocks.push([time, statData.pools[poolName].blocks.pending]) 184 | } else { 185 | pool.hashrate.push([time, 0]); 186 | pool.workers.push([time, 0]); 187 | pool.blocks.push([time, 0]) 188 | } 189 | if (callback != null) { 190 | callback(pool); 191 | } 192 | } else { 193 | buildPoolData(statData, poolName, callback); 194 | } 195 | } 196 | 197 | //updates stat data objects for workers stored within the cache 198 | var updateWorkerData = function(statData, workerData, address, callback = null) { 199 | //TODO 200 | } 201 | 202 | function getWorkerNameFromAddress(w) { 203 | var worker = w; 204 | if (w.split(".").length > 1) { 205 | worker = w.split(".")[1]; 206 | if (worker == null || worker.length < 1) { 207 | worker = "noname"; 208 | } 209 | } else { 210 | worker = "noname"; 211 | } 212 | return worker; 213 | } 214 | -------------------------------------------------------------------------------- /website/static/nvd3.css: -------------------------------------------------------------------------------- 1 | .chartWrap{margin:0;padding:0;overflow:hidden}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 250ms linear;-moz-transition:opacity 250ms linear;-webkit-transition:opacity 250ms linear;transition-delay:250ms;-moz-transition-delay:250ms;-webkit-transition-delay:250ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{position:absolute;pointer-events:none}svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:block;width:100%;height:100%}svg text{font:400 12px Arial}svg .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .disabled circle{fill-opacity:0}.nvd3 .nv-axis{pointer-events:none}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-bars .negative rect{zfill:brown}.nvd3 .nv-bars rect{zfill:#4682b4;fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups path.nv-line{fill:none;stroke-width:1.5px}.nvd3 .nv-groups path.nv-line.nv-thin-line{stroke-width:1px}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3 .nv-line.hover path{stroke-width:6px}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}.nvd3 .nv-distribution{pointer-events:none}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:4px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel{font-weight:700}.nvd3.nv-historicalStockChart .nv-dragTarget{fill-opacity:0;stroke:none;cursor:move}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-indentedtree .name{margin-left:5px}.nvd3.nv-indentedtree .clickable{color:#08C;cursor:pointer}.nvd3.nv-indentedtree span.clickable:hover{color:#005580;text-decoration:underline}.nvd3.nv-indentedtree .nv-childrenCount{display:inline-block;margin-left:5px}.nvd3.nv-indentedtree .nv-treeicon{cursor:pointer}.nvd3.nv-indentedtree .nv-treeicon.nv-folded{cursor:pointer}.nvd3 .background path{fill:none;stroke:#ccc;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke:#4682b4;stroke-opacity:.7}.nvd3 .brush .extent{fill-opacity:.3;stroke:#fff;shape-rendering:crispEdges}.nvd3 .axis line,.axis path{fill:none;stroke:#000;shape-rendering:crispEdges}.nvd3 .axis text{text-shadow:0 1px 0 #fff}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} -------------------------------------------------------------------------------- /libs/api.js: -------------------------------------------------------------------------------- 1 | var redis = require('redis'); 2 | var async = require('async'); 3 | 4 | const functions = require('./functions.js'); 5 | 6 | /*var JSONStream = require('JSONStream'); 7 | var es = require('event-stream');*/ 8 | 9 | var stats = require('./stats.js'); 10 | 11 | 12 | const loggerFactory = require('./logger.js'); 13 | 14 | const logger = loggerFactory.getLogger('Api', 'system'); 15 | 16 | 17 | 18 | module.exports = function(portalConfig, poolConfigs) { 19 | 20 | var _this = this; 21 | 22 | var portalStats = this.stats = new stats(portalConfig, poolConfigs); 23 | 24 | this.liveStatConnections = {}; 25 | 26 | this.handleApiRequest = function(req, res, next) { 27 | switch (req.params.method) { 28 | case 'stats': 29 | res.header('Content-Type', 'application/json'); 30 | res.end(portalStats.statsString); 31 | return; 32 | case 'getblocksstats': 33 | portalStats.getBlocks(function(data){ 34 | res.header('Content-Type', 'application/json'); 35 | res.end(JSON.stringify(data)); 36 | }); 37 | break; 38 | case 'payments': 39 | var poolBlocks = []; 40 | for(var pool in portalStats.stats.pools) { 41 | poolBlocks.push({name: pool, pending: portalStats.stats.pools[pool].pending, payments: portalStats.stats.pools[pool].payments}); 42 | } 43 | res.header('Content-Type', 'application/json'); 44 | res.end(JSON.stringify(poolBlocks)); 45 | return; 46 | case 'worker_stats': 47 | res.header('Content-Type', 'application/json'); 48 | if (req.url.indexOf("?") > 0) { 49 | var url_parms = req.url.split("?"); 50 | if (url_parms.length > 0) { 51 | var history = {}; 52 | var workers = {}; 53 | var address = url_parms[1] || null; 54 | //res.end(portalStats.getWorkerStats(address)); 55 | if (address != null && address.length > 0) { 56 | // make sure it is just the miners address 57 | address = address.split(".")[0]; 58 | // get miners balance along with worker balances 59 | portalStats.getBalanceByAddress(address, function(balances) { 60 | 61 | // get current round share total 62 | portalStats.getTotalSharesByAddress(address, function(shares) { 63 | 64 | var totalHash = parseFloat(0.0); 65 | var totalHeld = parseFloat(0.0); 66 | var totalShares = shares; 67 | var networkSols = 0; 68 | 69 | for (var h in portalStats.statHistory) { 70 | for (var pool in portalStats.statHistory[h].pools) { 71 | for (var w in portalStats.statHistory[h].pools[pool].workers) { 72 | if (w.startsWith(address)) { 73 | if (history[w] == null) { 74 | history[w] = []; 75 | } 76 | if (portalStats.statHistory[h].pools[pool].workers[w].hashrate) { 77 | history[w].push({ 78 | time: portalStats.statHistory[h].time, 79 | hashrate: portalStats.statHistory[h].pools[pool].workers[w].hashrate 80 | }); 81 | } 82 | } 83 | } 84 | // order check... 85 | //console.log(portalStats.statHistory[h].time); 86 | } 87 | } 88 | 89 | for (var pool in portalStats.stats.pools) { 90 | for (var w in portalStats.stats.pools[pool].workers) { 91 | 92 | if (w.startsWith(address)) { 93 | 94 | // console.log('>>>>BALANCES>>>>: ' + JSON.stringify(balances)); 95 | 96 | workers[w] = portalStats.stats.pools[pool].workers[w]; 97 | 98 | for (var b in balances.balances) { 99 | if (w == balances.balances[b].worker) { 100 | workers[w].paid = balances.balances[b].paid; 101 | workers[w].balance = balances.balances[b].balance; 102 | workers[w].immature = balances.balances[b].immature; 103 | } 104 | } 105 | workers[w].balance = (workers[w].balance || 0); 106 | workers[w].immature = (workers[w].immature || 0); 107 | workers[w].paid = (workers[w].paid || 0); 108 | 109 | // console.log('workers[w].immature: '+workers[w].immature); 110 | 111 | totalHash += portalStats.stats.pools[pool].workers[w].hashrate; 112 | networkSols = portalStats.stats.pools[pool].poolStats.networkSols; 113 | 114 | } 115 | 116 | } 117 | 118 | } 119 | res.end(JSON.stringify({ 120 | miner: address, 121 | totalHash: totalHash, 122 | totalShares: totalShares, 123 | networkSols: networkSols, 124 | 125 | immature: (balances.totalImmature * 100000000), 126 | balance: balances.totalHeld, 127 | paid: balances.totalPaid, 128 | 129 | workers: workers, 130 | history: history 131 | })); 132 | 133 | }); 134 | 135 | 136 | }); 137 | } else { 138 | res.end(JSON.stringify({ 139 | result: "error" 140 | })); 141 | } 142 | } else { 143 | res.end(JSON.stringify({ 144 | result: "error" 145 | })); 146 | } 147 | } else { 148 | res.end(JSON.stringify({ 149 | result: "error" 150 | })); 151 | } 152 | return; 153 | case 'pool_fees': 154 | res.header('Content-Type', 'application/json'); 155 | 156 | /* leshacat code :) */ 157 | var o = { pools : [] }; // empty Object 158 | 159 | for (var pool in poolConfigs) { 160 | 161 | var ttotal = 0.0; 162 | 163 | var rewardRecipients = portalStats.stats.pools[pool].rewardRecipients || {}; 164 | for (var r in rewardRecipients) { 165 | ttotal += rewardRecipients[r]; 166 | } 167 | 168 | 169 | var intSec = poolConfigs[pool].paymentProcessing.paymentInterval || 0; 170 | var intMinPymt = poolConfigs[pool].paymentProcessing.minimumPayment || 0; 171 | var strSchema = poolConfigs[pool].paymentProcessing.schema || "PROP"; 172 | 173 | tmpStr = functions.secToDHMSStr(intSec); 174 | 175 | o.pools.push({"coin":pool, "fee": ttotal, "payoutscheme":strSchema, "interval":intSec, "intervalstr":tmpStr, "minimum": intMinPymt}); // 176 | 177 | } 178 | res.end(JSON.stringify(o)); 179 | 180 | return; 181 | case 'pool_stats': 182 | res.header('Content-Type', 'application/json'); 183 | 184 | res.end(JSON.stringify(portalStats.statPoolHistory)); 185 | 186 | return; 187 | case 'live_stats': 188 | res.writeHead(200, { 189 | 'Content-Type': 'text/event-stream', 190 | 'Cache-Control': 'no-cache', 191 | 'Connection': 'keep-alive' 192 | }); 193 | res.write('\n'); 194 | var uid = Math.random().toString(); 195 | _this.liveStatConnections[uid] = res; 196 | res.flush(); 197 | req.on("close", function() { 198 | delete _this.liveStatConnections[uid]; 199 | }); 200 | default: 201 | next(); 202 | } 203 | }; 204 | 205 | Object.filter = (obj, predicate) => 206 | Object.keys(obj) 207 | .filter( key => predicate(obj[key]) ) 208 | .reduce( (res, key) => (res[key] = obj[key], res), {} ); 209 | 210 | this.handleAdminApiRequest = function(req, res, next) { 211 | switch (req.params.method) { 212 | case 'pools': 213 | { 214 | res.end(JSON.stringify({ 215 | result: poolConfigs 216 | })); 217 | return; 218 | } 219 | default: 220 | next(); 221 | } 222 | }; 223 | 224 | }; 225 | -------------------------------------------------------------------------------- /website/static/miner_stats.js: -------------------------------------------------------------------------------- 1 | var workerHashrateData; 2 | var workerHashrateChart; 3 | var workerHistoryMax = 160; 4 | 5 | var statData; 6 | var totalHash; 7 | var totalImmature; 8 | var totalBal; 9 | var totalPaid; 10 | var totalShares; 11 | var alerted = false; 12 | var shareGage; 13 | var invalidGage; 14 | var workerGage; 15 | var hashGage; 16 | 17 | function getWorkerNameFromAddress(w) { 18 | var worker = w; 19 | if (w.split(".").length > 1) { 20 | worker = w.split(".")[1]; 21 | if (worker == null || worker.length < 1) { 22 | worker = "noname"; 23 | } 24 | } else { 25 | worker = "noname"; 26 | } 27 | return worker; 28 | } 29 | 30 | function displayCharts() { 31 | var stats = getWorkerStats(_miner); 32 | shareGage = new JustGage({ 33 | id: "gauge", 34 | value: stats.currRoundShares > 0 ? Math.floor((stats.currRoundShares / stats.currRoundPoolShares) * 100) : 0, 35 | min: 0, 36 | max: 100, 37 | symbol: '%', 38 | pointer: true, 39 | counter: true, 40 | decimals: 2, 41 | pointerOptions: { 42 | toplength: -15, 43 | bottomlength: 10, 44 | bottomwidth: 12, 45 | color: '#8e8e93', 46 | stroke: '#ffffff', 47 | stroke_width: 3, 48 | stroke_linecap: 'round' 49 | }, 50 | title: "Shares This Round", 51 | gaugeWidthScale: 0.6, 52 | levelColors:["#e8e84c", "#6cdb5e"], 53 | levelColorsGradient: true 54 | }); 55 | 56 | var tmpInt = Math.min((((10000 * stats.shares / (stats.shares + stats.invalidShares)) / 100)), 100); 57 | var tmpInt2 = (100 - tmpInt) || 0; 58 | 59 | invalidGage = new JustGage({ 60 | id: "validShare", 61 | value: tmpInt2, 62 | min: 0, 63 | max: 100, 64 | symbol: '%', 65 | pointer: true, 66 | counter: true, 67 | decimals: 2, 68 | pointerOptions: { 69 | toplength: -15, 70 | bottomlength: 10, 71 | bottomwidth: 12, 72 | color: '#8e8e93', 73 | stroke: '#ffffff', 74 | stroke_width: 3, 75 | stroke_linecap: 'round' 76 | }, 77 | title: "Invalid Shares", 78 | gaugeWidthScale: 0.6, 79 | levelColors:["#f9a42c", "#f21f10"], 80 | levelColorsGradient: true 81 | }); 82 | workerGage= new JustGage({ 83 | id: "workerDominance", 84 | value: stats.miners ? (Object.keys(stats.miners).length / stats.poolSize) * 100 : 0, 85 | min: 0, 86 | max: 100, 87 | symbol: '%', 88 | pointer: true, 89 | counter: true, 90 | decimals: 2, 91 | pointerOptions: { 92 | toplength: -15, 93 | bottomlength: 10, 94 | bottomwidth: 12, 95 | color: '#8e8e93', 96 | stroke: '#ffffff', 97 | stroke_width: 3, 98 | stroke_linecap: 'round' 99 | }, 100 | title: "Worker Dominance", 101 | gaugeWidthScale: 0.6, 102 | levelColors:["#e8e84c", "#6cdb5e"], 103 | levelColorsGradient: true 104 | }); 105 | var high = 0; 106 | console.log(stats.hashrate); 107 | hashGage = new JustGage({ 108 | id: "hashDominance", 109 | value: stats.hashrate > 0 ? (stats.hashrate / stats.poolHashrate) * 100 : 0, 110 | min: 0, 111 | max: 100, 112 | symbol: '%', 113 | title: "Hashrate Dominance", 114 | levelColors:["#e8e84c", "#6cdb5e"], 115 | levelColorsGradient: true, 116 | pointer: true, 117 | counter: true, 118 | decimals: 2, 119 | pointerOptions: { 120 | toplength: -15, 121 | bottomlength: 10, 122 | bottomwidth: 12, 123 | color: '#8e8e93', 124 | stroke: '#ffffff', 125 | stroke_width: 3, 126 | stroke_linecap: 'round' 127 | }, 128 | gaugeWidthScale: 0.6 129 | }); 130 | var maxScale = 0; 131 | var label = 'H/s'; 132 | for (var w in stats.miners) { 133 | var pair = getReadableHashRatePair(Math.max.apply(null, stats.miners[w].hashrate.map(x => x[1]))); 134 | var i = pair[2]; 135 | if (maxScale < i) { 136 | maxScale = i; 137 | label = pair[1]; 138 | } 139 | } 140 | var dataset = []; 141 | for (var d in stats.miners) { 142 | var data = stats.miners[d]; 143 | var color = getRandomPastelColor(); 144 | var o = { 145 | label: data.key, 146 | fill: false, 147 | data: data.hashrate.map(x => { 148 | return { 149 | t: x[0], 150 | y: getScaledHashrate(x[1], i) 151 | } 152 | }), 153 | borderWidth: 2, 154 | backgroundColor: color, 155 | borderColor: color 156 | }; 157 | dataset.push(o); 158 | } 159 | 160 | workerHashrateChart = createDefaultLineChart( 161 | document.getElementById("workerHashChart").getContext('2d'), 162 | dataset, 163 | 'Time', 164 | label 165 | ); 166 | } 167 | 168 | function updateStats() { 169 | 170 | var stats = getWorkerStats(_miner); 171 | 172 | totalHash = stats.hashrate; 173 | totalShares = stats.totalShares; 174 | 175 | // update miner stats 176 | $("#statsHashrate").text(getReadableHashRateString(totalHash)); 177 | $("#statsHashrateAvg").text(getReadableHashRateString(calculateAverageHashrate(null))); 178 | 179 | /* $("#statsTotalImmature").text(totalImmature); 180 | $("#statsTotalBal").text(totalBal); 181 | $("#statsTotalPaid").text(totalPaid);*/ 182 | 183 | } 184 | 185 | function updateWorkerStats() { 186 | var stats = getWorkerStats(_miner); 187 | // update worker stats 188 | var i = 0; 189 | for (var w in stats.miners) { 190 | i++; 191 | var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, ''); 192 | var saneWorkerName = getWorkerNameFromAddress(w); 193 | console.log(stats.miners[w]); 194 | $("#statsHashrate" + htmlSafeWorkerName).text(getReadableHashRateString(stats.miners[w].hashrate[stats.miners[w].hashrate.length - 1] || 0)); 195 | $("#statsHashrateAvg" + htmlSafeWorkerName).text(getReadableHashRateString(calculateAverageHashrate(saneWorkerName))); 196 | 197 | /* $("#statsTotalImmature").text(totalImmature); 198 | $("#statsTotalBal").text(totalBal); 199 | $("#statsTotalPaid").text(totalPaid);*/ 200 | 201 | } 202 | } 203 | 204 | function addWorkerToDisplay(name, htmlSafeName, workerObj) { 205 | var htmlToAdd = ""; 206 | htmlToAdd = '
    '; 207 | htmlToAdd += '
    ' + name.replace(/[^\w\s]/gi, '') + '
    '; 208 | htmlToAdd += '
    ' + getReadableHashRateString(workerObj.hashrate[workerObj.hashrate.length - 1][1] || 0) + ' (Now)
    '; 209 | htmlToAdd += '
    ' + getReadableHashRateString(calculateAverageHashrate(name)) + ' (Avg)
    '; 210 | htmlToAdd += '
    '; 211 | 212 | $("#boxesWorkers").html($("#boxesWorkers").html() + htmlToAdd); 213 | 214 | } 215 | 216 | function calculateAverageHashrate(worker) { 217 | var stats = getWorkerStats(_miner); 218 | var count = 0; 219 | var total = 1; 220 | var avg = 0; 221 | for (w in stats.miners) { 222 | count = 0; 223 | for (var ii = 0; ii < stats.miners[w].hashrate.length; ii++) { 224 | if (worker == null || stats.miners[w].key === worker) { 225 | count++; 226 | avg += parseFloat(stats.miners[w].hashrate[ii][1]); 227 | } 228 | } 229 | if (count > total) 230 | total = count; 231 | } 232 | avg = avg / total; 233 | return avg; 234 | } 235 | 236 | 237 | function rebuildWorkerDisplay() { 238 | var stats = getWorkerStats(_miner); 239 | $("#boxesWorkers").html(""); 240 | var i = 0; 241 | for (var w in stats.miners) { 242 | i++; 243 | var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, ''); 244 | var saneWorkerName = getWorkerNameFromAddress(w); 245 | addWorkerToDisplay(saneWorkerName, htmlSafeWorkerName, stats.miners[w]); 246 | } 247 | } 248 | 249 | 250 | // grab initial stats 251 | $.getJSON('/api/worker_stats?' + _miner, function(data) { 252 | if (document.hidden) return; 253 | $.getJSON('/api/pool_stats', function(statData) { 254 | addWorkerToTracker(statData, data, _miner, function(){ 255 | 256 | 257 | //alert("MS> Locating worker stats for: " + _miner); 258 | 259 | var stats = getWorkerStats(_miner); 260 | 261 | statData = data; 262 | 263 | for (var w in statData.workers) { 264 | _workerCount++; 265 | } 266 | 267 | displayCharts(); 268 | rebuildWorkerDisplay(); 269 | updateStats(); 270 | 271 | var totalPaid = statData.paid || 0; 272 | var totalBal = statData.balance || 0; 273 | var totalImmature = (statData.immature) || 0; 274 | 275 | //alert('immature: ' + totalImmature); 276 | 277 | var luckDays = statData.luckDays || "unknown"; 278 | 279 | var SYMB = stats.symbol || "unknown symbol"; 280 | 281 | $('#total-paid-label').append(totalPaid.toFixed(8) + ' ' + SYMB); 282 | 283 | $('#total-immature-label').append(totalImmature.toFixed(8) + ' ' + SYMB); 284 | $('#total-balance-label').append(totalBal.toFixed(8) + ' ' + SYMB); 285 | 286 | $('#total-luckdays-label').append(luckDays); 287 | 288 | }); 289 | }); 290 | }); 291 | 292 | 293 | // live stat updates 294 | statsSource.addEventListener('message', function(e) { 295 | var stats = JSON.parse(e.data); 296 | $.getJSON('/api/worker_stats?' + _miner, function(data) { 297 | //$('#total-paid-label').empty(); 298 | //$('#total-paid-label').append(total.toFixed(8) + ' ' + symbol); 299 | }); 300 | }); 301 | --------------------------------------------------------------------------------