├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── aux_configs └── README.md ├── coins ├── aced.json ├── dixicoin.json ├── methuselah.json ├── qbiccoin.json ├── qudexcoin.json ├── ravencoin.json ├── scribe.json └── wavicoin.json ├── config.json ├── config_example.json ├── docker ├── Dockerfile ├── build │ └── config.gypi └── config │ ├── config_example.json │ └── pool_configs │ └── litecoin_example.json ├── init.js ├── libs ├── api.js ├── apiBittrex.js ├── apiCoinWarz.js ├── apiCryptsy.js ├── apiMintpal.js ├── apiPoloniex.js ├── cliListener.js ├── logger.js ├── mongoCompatibility.js ├── mposCompatibility.js ├── paymentProcessor.js ├── poolWorker.js ├── profitSwitch.js ├── shareProcessor.js ├── stats.js ├── website.js └── workerapi.js ├── logs └── .gitkeep ├── package-lock.json ├── package.json ├── pool_configs ├── aced.json ├── litecoin_example.json ├── methuselah.json ├── scribe.json └── vertcoin_example.json ├── scripts ├── blocknotify ├── blocknotify.c └── cli.js └── website ├── index.html ├── key.html ├── pages ├── admin.html ├── api.html ├── blocks.html ├── dashboard.html ├── getting_started.html ├── history.html ├── home.html ├── learn_more.html ├── miner_stats.html ├── mining_key.html ├── pool_stats.html ├── pools.html ├── stats.html └── tbs.html └── static ├── admin.js ├── dashboard.js ├── favicon.png ├── highchart.js ├── icons ├── aced.png ├── dixicoin.png ├── methuselah.png ├── qbiccoin.png ├── qudex.png ├── ravencoin.png ├── scribe.png └── wavicoin.png ├── img ├── favicon.png ├── my-banner.png └── noto-hash-banner.png ├── justgage.js ├── logo.svg ├── main.js ├── methods.js ├── miner_stats.js ├── nvd3.css ├── nvd3.js ├── pool_stats.js ├── raphael-2.1.4.min.js ├── stat_tracker.js ├── stats.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | config.json 4 | pool_configs/*.json 5 | !pool_configs/litecoin_example.json 6 | !pool_configs/vertcoin_example.json 7 | logs/ 8 | !.gitkeep -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.0.0 2 | 3 | * Started versioning 4 | * Fixed payments functionality https://github.com/foxer666/node-stratum-pool/pull/6 5 | 6 | ## v1.0.1 7 | 8 | * Stratum module update https://github.com/foxer666/node-stratum-pool/pull/7 9 | 10 | ## v1.0.2 11 | * Added x16r (ravencoin) support 12 | 13 | ## v1.0.3 14 | * Logging switched to winston 15 | * https://github.com/foxer666/node-open-mining-portal/issues/36 16 | * https://github.com/foxer666/node-open-mining-portal/issues/37 17 | * Onexcoin 18 | 19 | ## v1.0.4 20 | * https://github.com/foxer666/node-open-mining-portal/issues/40 21 | 22 | ## v1.0.5 23 | * https://github.com/foxer666/node-open-mining-portal/issues/42 24 | 25 | ## v1.0.6 26 | * https://github.com/foxer666/node-open-mining-portal/issues/36 hotfix 27 | 28 | ## v1.0.7 29 | * https://github.com/foxer666/node-open-mining-portal/issues/36 hotfix -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Helping give NOMP new life! With some style. 2 | #### If you want to help contribute, please look at the original [project](https://github.com/foxer666/node-open-mining-portal) first! 3 | 4 | This is all the things great about NOMP, now with modern style and design of BootStrap 4! Having disliked the previous UI, due to cramping, little to no info displayed while what is displayed is not very helpful to users. I decided to fork a already great project, and add some stylistic touches it severely needs. I would of considered requesting pulls to the main branch, though with such heavy changes to the project, I decided a hard fork would be well suited. But if you would like to contribute, please consider looking at the [original project](https://github.com/foxer666/node-open-mining-portal) first, as these guys are the ones who helped get this NOMP rebirthing process started. 5 | 6 | ------- 7 | ### Node Open Mining Portal consists from 3 main modules: 8 | * [NOMP](https://github.com/cryptosharks131/NiceNOMP) 9 | * [Stratum Pool](https://github.com/cryptosharks131/node-stratum-pool) 10 | * [Node Multihashing](https://github.com/cryptosharks131/node-multi-hashing) 11 | 12 | _Stratum Pool can be replaced with [node-merged-pool](https://github.com/UNOMP/node-merged-pool)._
13 | _Add new algorithms using [Node Multihashing](https://github.com/cryptosharks131/node-multi-hashing)._ 14 | 15 | Current version: v1.1.3 16 | 17 | ------- 18 | ### Recommended Dependencies/Setup Ubuntu 16.04 19 | ``` 20 | sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev 21 | sudo apt-get install libboost-all-dev git npm nodejs nodejs-legacy libminiupnpc-dev redis-server 22 | sudo apt-get install -y software-properties-common 23 | sudo add-apt-repository ppa:bitcoin/bitcoin 24 | sudo apt-get update 25 | sudo apt-get install libdb4.8-dev libdb4.8++-dev 26 | sudo apt-get -y install fail2ban 27 | sudo systemctl enable fail2ban 28 | sudo systemctl start fail2ban 29 | 30 | sudo npm install -g n 31 | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - 32 | sudo apt-get install -y nodejs 33 | ``` 34 | 35 | ------- 36 | ### Install 37 | ``` 38 | git clone https://github.com/cryptosharks131/NiceNOMP.git pool 39 | sudo chmod a+rwx pool 40 | cd pool 41 | sudo npm install 42 | sudo node init.js 43 | ``` 44 | ------- 45 | ### Requirements 46 | * Node 8.x.x or higher 47 | * Coin daemon 48 | * Redis Server 49 | 50 | ------- 51 | ### Hashing algorithms 52 | #### Working 53 | | | Algorithm | Comment| 54 | | ------------- | ------------- | ------------- | 55 | | ✓ | __C11__ | tested shares and payments with Dixicoin | 56 | | ✓ | __Groestl__ | tested only shares with AuroraCoin, blocks not tested | 57 | | ✓ | __lyra2rev2__ | shares work, needs tests with payments. currently being tested with Lunex coin | 58 | | ✓ | __lyra2z__ | Working in testnet *mining* and *payouts* | 59 | | ✓ | __NeoScrypt__ | working now thanks to @foxer666 pushing update to parent repo | 60 | | ✓ | __Qubit__ | Shares works, and blocks should now too. | 61 | | ✓ | __Scrypt__ | tested with AntiLiteCoin, 1CREDIT, ArgusCoin, WAYAWOLFCOIN and many others | 62 | | ✓ | __SHA256__ | tested with VCOIN, don't use with BTC, no Segwit tested | 63 | | ✓ | __X11__ | tested with BrainCoin, CannabisCoin, AdzCoin and many others | 64 | | ✓ | __X16r__ | tested with RavenCoin | 65 | | ✓ | __X16s__ | tested with AceD | 66 | | ✓ | __Yescrypt__ | needs tests, though should work | 67 | | ✓ | __YescryptR16__ | needs tests, though should work | 68 | | ✓ | __YescryptR32__ | currently being tested with WaviCoin. shares work, payments unconfirmed | 69 | 70 | #### Need tests 71 | | | Algorithm | Comment| 72 | | ------------- | ------------- | ------------- | 73 | | ? | __Argon2__ | need tests | 74 | | ? | __Blake__ | need tests | 75 | | ? | __Blake2S__ | need tests | 76 | | ? | __Cryptonight__ | need tests | 77 | | ? | __Dcrypt__ | need tests | 78 | | ? | __Decred__ | need tests | 79 | | ? | __Fresh__ | need tests | 80 | | ? | __Fugue__ | need tests | 81 | | ? | __GroestlMyriad__ | need tests | 82 | | ? | __Quark__ | need tests | 83 | | ? | __Hefty1__ | need tests | 84 | | ? | __Keccak__ | need tests | 85 | | ? | __Lbry__ | need tests | 86 | | ? | __lyra2re__ | need tests | 87 | | ? | __lyra2re2__ | need tests | 88 | | x | __lyra2z330__ | need tests | 89 | | ? | __NIST5__ | need tests | 90 | | ? | __S3__ | need tests | 91 | | ? | __Scrypt-N__ | need tests | 92 | | ? | __Scrypt-OG__ | need tests | 93 | | ? | __Sha1__ | need tests | 94 | | ? | __SHAvite-3__ | need tests | 95 | | ? | __Skein__ | need tests | 96 | | ? | __X11Ghost__ | need tests | 97 | | ? | __X13__ | need tests | 98 | | ? | __X14__ | need tests | 99 | | ? | __X15__ | need tests | 100 | | ? | __zr5__ | need tests | 101 | | ? | __ziftr__ | need tests | 102 | 103 | #### Don't work yet 104 | | | Algorithm | Comment| 105 | | ------------- | ------------- | ------------- | 106 | | - | __Scrypt-Jane__ | submitblock not working tested with CacheCoin, Yacoin | 107 | 108 | ------- 109 | 110 | ------- 111 | ### Credits 112 | * [1301313Y](//github.com/1301313Y) - Upgraded the UI and stat modules 113 | * [a2hill](//github.com/a2hill) - helped with X16r 114 | * [devnulled](//github.com/devnull-ed) - helped with lyra2z, neoscrypt algo 115 | * [Kris Klosterman / krisklosterman](https://github.com/krisklosterman) - Updated code for work wiht Node.JS >=8 116 | * [Jerry Brady / mintyfresh68](https://github.com/bluecircle) - got coin-switching fully working and developed proxy-per-algo feature 117 | * [Tony Dobbs](http://anthonydobbs.com) - designs for front-end and created the NOMP logo 118 | * [LucasJones](//github.com/LucasJones) - got p2p block notify working and implemented additional hashing algos 119 | * [vekexasia](//github.com/vekexasia) - co-developer & great tester 120 | * [TheSeven](//github.com/TheSeven) - answering an absurd amount of my questions and being a very helpful gentleman 121 | * [UdjinM6](//github.com/UdjinM6) - helped implement fee withdrawal in payment processing 122 | * [Alex Petrov / sysmanalex](https://github.com/sysmanalex) - contributed the pure C block notify script 123 | * [svirusxxx](//github.com/svirusxxx) - sponsored development of MPOS mode 124 | * [icecube45](//github.com/icecube45) - helping out with the repo wiki 125 | * [Fcases](//github.com/Fcases) - ordered me a pizza <3 126 | * [yoshuki43](//github.com/yoshuki43) - his K-Nomp project has really help the development! 127 | * Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool#credits) 128 | 129 | ------- 130 | ### License 131 | Released under the GNU General Public License v2 132 | http://www.gnu.org/licenses/gpl-2.0.html 133 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Todo list 2 | 3 | Order is priority 4 | 1) Fix neoscrypt (Almost ready) 5 | 3) Rewrite payments module + tests 6 | 4) Add option to make an manual payments rather than automatic (for pools with big network diff) 7 | 5) New frontend 8 | 6) Move modules in one project 9 | 7) Write different log level at one time 10 | 8) Documentation -------------------------------------------------------------------------------- /aux_configs/README.md: -------------------------------------------------------------------------------- 1 | **DO NOT USE THIS DIRECTORY! 2 | -------------------------------------------------------------------------------- /coins/aced.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AceD", 3 | "symbol": "ACED", 4 | "algorithm": "x11", 5 | "explorerGetBlockJSON": "http://explorer.acedcoin.com/api/getblock?hash=", 6 | "explorerGetBlock": "http://explorer.acedcoin.com/block/", 7 | "explorerGetTX": "http://explorer.acedcoin.com/api/getrawtransaction?txid={0}&decrypt=1", 8 | "blockTime": 120 9 | } 10 | -------------------------------------------------------------------------------- /coins/dixicoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dixicoin", 3 | "symbol": "DXC", 4 | "algorithm": "c11", 5 | "explorerGetBlockJSON": "http://www.dixi.live/api/getblock?hash=", 6 | "explorerGetBlock": "http://www.dixi.live/block/", 7 | "explorerGetTX": "http://www.dixi.live/api/getrawtransaction?txid={0}&decrypt=1", 8 | "blockTime": 60 9 | } 10 | -------------------------------------------------------------------------------- /coins/methuselah.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Methuselah", 3 | "symbol": "SAP", 4 | "algorithm": "lyra2rev2", 5 | "explorerGetBlockJSON": "https://explorer.methuselahcoin.io/api/block/", 6 | "explorerGetBlock": "https://explorer.methuselahcoin.io/#/block/", 7 | "explorerGetTX": "https://explorer.methuselahcoin.io/api/tx/{0}", 8 | "blockTime": 135 9 | } 10 | -------------------------------------------------------------------------------- /coins/qbiccoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Qbiccoin", 3 | "symbol": "QBIC", 4 | "algorithm": "neoscrypt", 5 | "explorerGetBlockJSON": "http://explorer.qbic.io:3001/api/getblock?hash=", 6 | "explorerGetBlock": "http://explorer.qbic.io:3001/block/", 7 | "explorerGetTX": "http://explorer.qbic.io:3001/api/getrawtransaction?txid={0}&decrypt=1", 8 | "blockTime": 120 9 | } 10 | -------------------------------------------------------------------------------- /coins/qudexcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "QuDex", 3 | "symbol": "QDP", 4 | "algorithm": "qubit", 5 | "explorerGetBlockJSON": "http://www.qudexpay.com:3001/api/getblock?hash=", 6 | "explorerGetBlock": "http://www.qudexpay.com:3001/block/", 7 | "explorerGetTX": "http://www.qudexpay.com:3001/api/getrawtransaction?txid={0}&decrypt=1", 8 | "blockTime": 60 9 | } 10 | -------------------------------------------------------------------------------- /coins/ravencoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RavenCoin", 3 | "symbol": "RVN", 4 | "algorithm": "x16r", 5 | "explorerGetBlockJSON": "http://www.threeeyed.info/api/getblock?hash=", 6 | "explorerGetBlock": "http://threeeyed.info/block/", 7 | "explorerGetTX": "http://www.threeeyed.info/api/getrawtransaction?txid={0}&decrypt=1", 8 | "blockTime": 60 9 | } 10 | -------------------------------------------------------------------------------- /coins/scribe.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scribe", 3 | "symbol": "SCRIBE", 4 | "algorithm": "lyra2rev2", 5 | "explorerGetBlockJSON": "http://explorer.scribe.network/api/getblock?hash=", 6 | "explorerGetBlock": "https://explorer.scribe.network/block/", 7 | "explorerGetTX": "http://explorer.scribe.network/api/getrawtransaction?txid={0}&decrypt=1", 8 | "blockTime": 90 9 | } 10 | -------------------------------------------------------------------------------- /coins/wavicoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wavicoin", 3 | "symbol": "WAVI", 4 | "algorithm": "yescryptR32", 5 | "explorerGetBlockJSON": "http://www.dixi.live/api/getblock?hash=", 6 | "explorerGetBlock": "http://www.dixi.live/block/", 7 | "explorerGetTX": "http://www.dixi.live/api/getrawtransaction?txid={0}&decrypt=1", 8 | "blockTime": 120 9 | } 10 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logger" : { 3 | "level" : "debug", 4 | "file" : "logs/nomp_debug.log" 5 | }, 6 | 7 | "cliHost": "127.0.0.1", 8 | "cliPort": 17117, 9 | 10 | "clustering": { 11 | "enabled": true, 12 | "forks": "auto" 13 | }, 14 | 15 | "defaultPoolConfigs": { 16 | "blockRefreshInterval": 1000, 17 | "jobRebroadcastTimeout": 55, 18 | "connectionTimeout": 600, 19 | "emitInvalidBlockHashes": false, 20 | "validateWorkerUsername": true, 21 | "tcpProxyProtocol": false, 22 | "banning": { 23 | "enabled": true, 24 | "time": 600, 25 | "invalidPercent": 50, 26 | "checkThreshold": 500, 27 | "purgeInterval": 300 28 | }, 29 | "redis": { 30 | "host": "127.0.0.1", 31 | "port": 6379 32 | } 33 | }, 34 | 35 | "website": { 36 | "enabled": true, 37 | "host": "IP_ADDRESS", 38 | "port": 80, 39 | "stratumHost": "FQDN", 40 | "stats": { 41 | "updateInterval": 60, 42 | "historicalRetention": 43200, 43 | "hashrateWindow": 300 44 | }, 45 | "adminCenter": { 46 | "enabled": true, 47 | "password": "PASSWORD" 48 | } 49 | }, 50 | 51 | "redis": { 52 | "host": "127.0.0.1", 53 | "port": 6379 54 | }, 55 | 56 | "switching": { 57 | "switch1": { 58 | "enabled": false, 59 | "algorithm": "sha256", 60 | "ports": { 61 | "3333": { 62 | "diff": 10, 63 | "varDiff": { 64 | "minDiff": 16, 65 | "maxDiff": 512, 66 | "targetTime": 15, 67 | "retargetTime": 90, 68 | "variancePercent": 30 69 | } 70 | } 71 | } 72 | }, 73 | "switch2": { 74 | "enabled": false, 75 | "algorithm": "scrypt", 76 | "ports": { 77 | "4444": { 78 | "diff": 10, 79 | "varDiff": { 80 | "minDiff": 16, 81 | "maxDiff": 512, 82 | "targetTime": 15, 83 | "retargetTime": 90, 84 | "variancePercent": 30 85 | } 86 | } 87 | } 88 | }, 89 | "switch3": { 90 | "enabled": false, 91 | "algorithm": "x11", 92 | "ports": { 93 | "5555": { 94 | "diff": 0.001, 95 | "varDiff": { 96 | "minDiff": 0.001, 97 | "maxDiff": 1, 98 | "targetTime": 15, 99 | "retargetTime": 60, 100 | "variancePercent": 30 101 | } 102 | } 103 | } 104 | } 105 | }, 106 | 107 | "profitSwitch": { 108 | "enabled": false, 109 | "updateInterval": 600, 110 | "depth": 0.90, 111 | "usePoloniex": true, 112 | "useCryptsy": true, 113 | "useMintpal": true, 114 | "useBittrex": true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "logger" : { 3 | "level" : "debug", 4 | "file" : "logs/nomp_debug.log" 5 | }, 6 | 7 | "cliHost": "127.0.0.1", 8 | "cliPort": 17117, 9 | 10 | "clustering": { 11 | "enabled": true, 12 | "forks": "auto" 13 | }, 14 | 15 | "defaultPoolConfigs": { 16 | "blockRefreshInterval": 1000, 17 | "jobRebroadcastTimeout": 55, 18 | "connectionTimeout": 600, 19 | "emitInvalidBlockHashes": false, 20 | "validateWorkerUsername": true, 21 | "tcpProxyProtocol": false, 22 | "banning": { 23 | "enabled": true, 24 | "time": 600, 25 | "invalidPercent": 50, 26 | "checkThreshold": 500, 27 | "purgeInterval": 300 28 | }, 29 | "redis": { 30 | "host": "127.0.0.1", 31 | "port": 6379 32 | } 33 | }, 34 | 35 | "website": { 36 | "enabled": true, 37 | "host": "0.0.0.0", 38 | "port": 80, 39 | "stratumHost": "cryppit.com", 40 | "stats": { 41 | "updateInterval": 60, 42 | "historicalRetention": 43200, 43 | "hashrateWindow": 300 44 | }, 45 | "adminCenter": { 46 | "enabled": true, 47 | "password": "password" 48 | } 49 | }, 50 | 51 | "redis": { 52 | "host": "127.0.0.1", 53 | "port": 6379 54 | }, 55 | 56 | "switching": { 57 | "switch1": { 58 | "enabled": false, 59 | "algorithm": "sha256", 60 | "ports": { 61 | "3333": { 62 | "diff": 10, 63 | "varDiff": { 64 | "minDiff": 16, 65 | "maxDiff": 512, 66 | "targetTime": 15, 67 | "retargetTime": 90, 68 | "variancePercent": 30 69 | } 70 | } 71 | } 72 | }, 73 | "switch2": { 74 | "enabled": false, 75 | "algorithm": "scrypt", 76 | "ports": { 77 | "4444": { 78 | "diff": 10, 79 | "varDiff": { 80 | "minDiff": 16, 81 | "maxDiff": 512, 82 | "targetTime": 15, 83 | "retargetTime": 90, 84 | "variancePercent": 30 85 | } 86 | } 87 | } 88 | }, 89 | "switch3": { 90 | "enabled": false, 91 | "algorithm": "x11", 92 | "ports": { 93 | "5555": { 94 | "diff": 0.001, 95 | "varDiff": { 96 | "minDiff": 0.001, 97 | "maxDiff": 1, 98 | "targetTime": 15, 99 | "retargetTime": 60, 100 | "variancePercent": 30 101 | } 102 | } 103 | } 104 | } 105 | }, 106 | 107 | "profitSwitch": { 108 | "enabled": false, 109 | "updateInterval": 600, 110 | "depth": 0.90, 111 | "usePoloniex": true, 112 | "useCryptsy": true, 113 | "useMintpal": true, 114 | "useBittrex": true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | RUN apt-get update 3 | RUN apt-get install -y sudo git nano curl 4 | 5 | WORKDIR /opt/ 6 | RUN git clone https://github.com/foxer666/node-open-mining-portal 7 | 8 | WORKDIR /opt/node-open-mining-portal 9 | RUN npm install 10 | RUN npm update 11 | RUN rm -rf pool_configs 12 | RUN rm config_example.json 13 | 14 | RUN ln -s /opt/config/config.json /opt/node-open-mining-portal/config.json 15 | RUN ln -s /opt/config/pool_configs /opt/node-open-mining-portal/pool_configs 16 | 17 | RUN apt-get install -y redis-server 18 | 19 | CMD service redis-server restart; node init.js -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /libs/api.js: -------------------------------------------------------------------------------- 1 | var redis = require('redis'); 2 | var async = require('async'); 3 | 4 | var stats = require('./stats.js'); 5 | 6 | 7 | const loggerFactory = require('./logger.js'); 8 | 9 | const logger = loggerFactory.getLogger('Api', 'system'); 10 | 11 | 12 | module.exports = function(portalConfig, poolConfigs) { 13 | 14 | var _this = this; 15 | 16 | var portalStats = this.stats = new stats(portalConfig, poolConfigs); 17 | 18 | this.liveStatConnections = {}; 19 | 20 | this.handleApiRequest = function(req, res, next) { 21 | switch (req.params.method) { 22 | case 'stats': 23 | res.header('Content-Type', 'application/json'); 24 | res.end(portalStats.statsString); 25 | return; 26 | case 'getblocksstats': 27 | portalStats.getBlocks(function(data){ 28 | res.header('Content-Type', 'application/json'); 29 | res.end(JSON.stringify(data)); 30 | }); 31 | break; 32 | case 'payments': 33 | var poolBlocks = []; 34 | for(var pool in portalStats.stats.pools) { 35 | poolBlocks.push({name: pool, pending: portalStats.stats.pools[pool].pending, payments: portalStats.stats.pools[pool].payments}); 36 | } 37 | res.header('Content-Type', 'application/json'); 38 | res.end(JSON.stringify(poolBlocks)); 39 | return; 40 | case 'worker_stats': 41 | res.header('Content-Type', 'application/json'); 42 | if (req.url.indexOf("?") > 0) { 43 | var url_parms = req.url.split("?"); 44 | if (url_parms.length > 0) { 45 | var history = {}; 46 | var workers = {}; 47 | var address = url_parms[1] || null; 48 | //res.end(portalStats.getWorkerStats(address)); 49 | if (address != null && address.length > 0) { 50 | // make sure it is just the miners address 51 | address = address.split(".")[0]; 52 | // get miners balance along with worker balances 53 | portalStats.getBalanceByAddress(address, function(balances) { 54 | // get current round share total 55 | portalStats.getTotalSharesByAddress(address, function(shares) { 56 | var totalHash = parseFloat(0.0); 57 | var totalShares = shares; 58 | var networkSols = 0; 59 | for (var h in portalStats.statHistory) { 60 | for (var pool in portalStats.statHistory[h].pools) { 61 | for (var w in portalStats.statHistory[h].pools[pool].workers) { 62 | if (w.startsWith(address)) { 63 | if (history[w] == null) { 64 | history[w] = []; 65 | } 66 | if (portalStats.statHistory[h].pools[pool].workers[w].hashrate) { 67 | history[w].push({ 68 | time: portalStats.statHistory[h].time, 69 | hashrate: portalStats.statHistory[h].pools[pool].workers[w].hashrate 70 | }); 71 | } 72 | } 73 | } 74 | // order check... 75 | //console.log(portalStats.statHistory[h].time); 76 | } 77 | } 78 | for (var pool in portalStats.stats.pools) { 79 | for (var w in portalStats.stats.pools[pool].workers) { 80 | if (w.startsWith(address)) { 81 | workers[w] = portalStats.stats.pools[pool].workers[w]; 82 | for (var b in balances.balances) { 83 | if (w == balances.balances[b].worker) { 84 | workers[w].paid = balances.balances[b].paid; 85 | workers[w].balance = balances.balances[b].balance; 86 | } 87 | } 88 | workers[w].balance = (workers[w].balance || 0); 89 | workers[w].paid = (workers[w].paid || 0); 90 | totalHash += portalStats.stats.pools[pool].workers[w].hashrate; 91 | networkSols = portalStats.stats.pools[pool].poolStats.networkSols; 92 | } 93 | } 94 | } 95 | res.end(JSON.stringify({ 96 | miner: address, 97 | totalHash: totalHash, 98 | totalShares: totalShares, 99 | networkSols: networkSols, 100 | immature: balances.totalImmature, 101 | balance: balances.totalHeld, 102 | paid: balances.totalPaid, 103 | workers: workers, 104 | history: history 105 | })); 106 | }); 107 | }); 108 | } else { 109 | res.end(JSON.stringify({ 110 | result: "error" 111 | })); 112 | } 113 | } else { 114 | res.end(JSON.stringify({ 115 | result: "error" 116 | })); 117 | } 118 | } else { 119 | res.end(JSON.stringify({ 120 | result: "error" 121 | })); 122 | } 123 | return; 124 | case 'pool_stats': 125 | res.header('Content-Type', 'application/json'); 126 | res.end(JSON.stringify(portalStats.statPoolHistory)); 127 | return; 128 | case 'live_stats': 129 | res.writeHead(200, { 130 | 'Content-Type': 'text/event-stream', 131 | 'Cache-Control': 'no-cache', 132 | 'Connection': 'keep-alive' 133 | }); 134 | res.write('\n'); 135 | var uid = Math.random().toString(); 136 | _this.liveStatConnections[uid] = res; 137 | res.flush(); 138 | req.on("close", function() { 139 | delete _this.liveStatConnections[uid]; 140 | }); 141 | default: 142 | next(); 143 | } 144 | }; 145 | 146 | Object.filter = (obj, predicate) => 147 | Object.keys(obj) 148 | .filter( key => predicate(obj[key]) ) 149 | .reduce( (res, key) => (res[key] = obj[key], res), {} ); 150 | 151 | this.handleAdminApiRequest = function(req, res, next) { 152 | switch (req.params.method) { 153 | case 'pools': 154 | { 155 | res.end(JSON.stringify({ 156 | result: poolConfigs 157 | })); 158 | return; 159 | } 160 | default: 161 | next(); 162 | } 163 | }; 164 | 165 | }; 166 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]); 92 | redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); 93 | redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]); 94 | } 95 | else if (shareData.blockHash){ 96 | redisCommands.push(['hincrby', coin + ':stats', 'invalidBlocks', 1]); 97 | } 98 | 99 | connection.multi(redisCommands).exec(function(err, replies){ 100 | if (err) 101 | logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err)); 102 | }); 103 | 104 | 105 | }; 106 | 107 | }; 108 | -------------------------------------------------------------------------------- /libs/website.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var async = require('async'); 5 | var watch = require('node-watch'); 6 | var redis = require('redis'); 7 | 8 | var dot = require('dot'); 9 | var express = require('express'); 10 | var bodyParser = require('body-parser'); 11 | var compress = require('compression'); 12 | 13 | var Stratum = require('stratum-pool'); 14 | var util = require('stratum-pool/lib/util.js'); 15 | 16 | var api = require('./api.js'); 17 | const loggerFactory = require('./logger.js'); 18 | const logger = loggerFactory.getLogger('Website', 'system'); 19 | 20 | module.exports = function () { 21 | logger.info("Starting Website module"); 22 | 23 | dot.templateSettings.strip = false; 24 | 25 | var portalConfig = JSON.parse(process.env.portalConfig); 26 | var poolConfigs = JSON.parse(process.env.pools); 27 | 28 | var websiteConfig = portalConfig.website; 29 | 30 | var portalApi = new api(portalConfig, poolConfigs); 31 | var portalStats = portalApi.stats; 32 | 33 | var logSystem = 'Website'; 34 | 35 | var pageFiles = { 36 | 'index.html': 'index', 37 | 'home.html': '', 38 | 'pools.html': 'pools', 39 | 'getting_started.html': 'getting_started', 40 | 'stats.html': 'stats', 41 | 'dashboard.html': 'dashboard', 42 | 'api.html': 'api', 43 | 'learn_more.html': 'learn_more', 44 | 'miner_stats.html': 'miner_stats', 45 | 'pool_stats.html': 'pool_stats', 46 | 'blocks.html': 'blocks' 47 | }; 48 | 49 | var pageTemplates = {}; 50 | 51 | var pageProcessed = {}; 52 | var indexesProcessed = {}; 53 | 54 | var keyScriptTemplate = ''; 55 | var keyScriptProcessed = ''; 56 | 57 | 58 | var processTemplates = function () { 59 | 60 | for (var pageName in pageTemplates) { 61 | if (pageName === 'index') continue; 62 | pageProcessed[pageName] = pageTemplates[pageName]({ 63 | poolsConfigs: poolConfigs, 64 | stats: portalStats.stats, 65 | portalConfig: portalConfig 66 | }); 67 | indexesProcessed[pageName] = pageTemplates.index({ 68 | page: pageProcessed[pageName], 69 | selected: pageName, 70 | stats: portalStats.stats, 71 | poolConfigs: poolConfigs, 72 | portalConfig: portalConfig 73 | }); 74 | } 75 | 76 | //logger.debug(logSystem, 'Stats', 'Website updated to latest stats'); 77 | }; 78 | 79 | 80 | var readPageFiles = function(files){ 81 | async.each(files, function(fileName, callback){ 82 | var filePath = 'website/' + (fileName === 'index.html' ? '' : 'pages/') + fileName; 83 | fs.readFile(filePath, 'utf8', function(err, data){ 84 | var pTemp = dot.template(data); 85 | pageTemplates[pageFiles[fileName]] = pTemp 86 | callback(); 87 | }); 88 | }, function(err){ 89 | if (err){ 90 | console.log('error reading files for creating dot templates: '+ JSON.stringify(err)); 91 | return; 92 | } 93 | processTemplates(); 94 | }); 95 | }; 96 | /* requires node-watch 0.5.0 or newer */ 97 | watch(['./website', './website/pages'], function(evt, filename){ 98 | var basename; 99 | // support older versions of node-watch automatically 100 | if (!filename && evt) 101 | basename = path.basename(evt); 102 | else 103 | basename = path.basename(filename); 104 | 105 | if (basename in pageFiles){ 106 | readPageFiles([basename]); 107 | logger.debug('Reloaded file %s', basename); 108 | } 109 | }); 110 | 111 | portalStats.getGlobalStats(function () { 112 | readPageFiles(Object.keys(pageFiles)); 113 | }); 114 | 115 | var buildUpdatedWebsite = function () { 116 | portalStats.getGlobalStats(function () { 117 | processTemplates(); 118 | 119 | var statData = 'data: ' + JSON.stringify(portalStats.stats) + '\n\n'; 120 | for (var uid in portalApi.liveStatConnections) { 121 | var res = portalApi.liveStatConnections[uid]; 122 | res.write(statData); 123 | } 124 | 125 | }); 126 | }; 127 | 128 | setInterval(buildUpdatedWebsite, websiteConfig.stats.updateInterval * 1000); 129 | 130 | 131 | var buildKeyScriptPage = function () { 132 | async.waterfall([ 133 | function (callback) { 134 | var client = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); 135 | client.hgetall('coinVersionBytes', function (err, coinBytes) { 136 | if (err) { 137 | client.quit(); 138 | return callback('Failed grabbing coin version bytes from redis ' + JSON.stringify(err)); 139 | } 140 | callback(null, client, coinBytes || {}); 141 | }); 142 | }, 143 | function (client, coinBytes, callback) { 144 | var enabledCoins = Object.keys(poolConfigs).map(function (c) { 145 | return c.toLowerCase() 146 | }); 147 | var missingCoins = []; 148 | enabledCoins.forEach(function (c) { 149 | if (!(c in coinBytes)) 150 | missingCoins.push(c); 151 | }); 152 | callback(null, client, coinBytes, missingCoins); 153 | }, 154 | function (client, coinBytes, missingCoins, callback) { 155 | var coinsForRedis = {}; 156 | async.each(missingCoins, function (c, cback) { 157 | var coinInfo = (function () { 158 | for (var pName in poolConfigs) { 159 | if (pName.toLowerCase() === c) 160 | return { 161 | daemon: poolConfigs[pName].paymentProcessing.daemon, 162 | address: poolConfigs[pName].address 163 | } 164 | } 165 | })(); 166 | var daemon = new Stratum.daemon.interface([coinInfo.daemon], logger); 167 | daemon.cmd('dumpprivkey', [coinInfo.address], function (result) { 168 | if (result[0].error) { 169 | logger.error('Could not dumpprivkey for %s , err = %s', c, JSON.stringify(result[0].error)); 170 | cback(); 171 | return; 172 | } 173 | 174 | var vBytePub = util.getVersionByte(coinInfo.address)[0]; 175 | var vBytePriv = util.getVersionByte(result[0].response)[0]; 176 | 177 | coinBytes[c] = vBytePub.toString() + ',' + vBytePriv.toString(); 178 | coinsForRedis[c] = coinBytes[c]; 179 | cback(); 180 | }); 181 | }, function (err) { 182 | callback(null, client, coinBytes, coinsForRedis); 183 | }); 184 | }, 185 | function (client, coinBytes, coinsForRedis, callback) { 186 | if (Object.keys(coinsForRedis).length > 0) { 187 | client.hmset('coinVersionBytes', coinsForRedis, function (err) { 188 | if (err) { 189 | logger.error('Failed inserting coin byte version into redis, err = %s', JSON.stringify(err)); 190 | } 191 | client.quit(); 192 | }); 193 | } 194 | else { 195 | client.quit(); 196 | } 197 | callback(null, coinBytes); 198 | } 199 | ], function (err, coinBytes) { 200 | if (err) { 201 | logger.error('Error, err = %s', err); 202 | return; 203 | } 204 | try { 205 | keyScriptTemplate = dot.template(fs.readFileSync('website/key.html', {encoding: 'utf8'})); 206 | keyScriptProcessed = keyScriptTemplate({coins: coinBytes}); 207 | } 208 | catch (e) { 209 | logger.error('Failed to read key.html file'); 210 | } 211 | }); 212 | 213 | }; 214 | buildKeyScriptPage(); 215 | 216 | var getPage = function (pageId) { 217 | if (pageId in pageProcessed) { 218 | var requestedPage = pageProcessed[pageId]; 219 | return requestedPage; 220 | } 221 | }; 222 | 223 | var poolStatPage = function(req, res, next) { 224 | var coin = req.params.coin || null; 225 | if (coin != null) { 226 | portalStats.getPoolStats(coin, function(){ 227 | processTemplates(); 228 | res.end(indexesProcessed['pool_stats']); 229 | }); 230 | } else { 231 | next(); 232 | } 233 | }; 234 | 235 | var minerpage = function(req, res, next){ 236 | var address = req.params.address || null; 237 | if (address != null) { 238 | address = address.split(".")[0]; 239 | portalStats.getBalanceByAddress(address, function(){ 240 | processTemplates(); 241 | res.header('Content-Type', 'text/html'); 242 | res.end(indexesProcessed['miner_stats']); 243 | }); 244 | } else { 245 | next(); 246 | } 247 | }; 248 | 249 | var route = function (req, res, next) { 250 | var pageId = req.params.page || ''; 251 | if (pageId in indexesProcessed) { 252 | res.header('Content-Type', 'text/html'); 253 | res.end(indexesProcessed[pageId]); 254 | } 255 | else 256 | next(); 257 | 258 | }; 259 | 260 | 261 | var app = express(); 262 | 263 | 264 | app.use(bodyParser.json()); 265 | 266 | app.get('/get_page', function (req, res, next) { 267 | var requestedPage = getPage(req.query.id); 268 | if (requestedPage) { 269 | res.end(requestedPage); 270 | return; 271 | } 272 | next(); 273 | }); 274 | 275 | app.get('/key.html', function (req, res, next) { 276 | res.end(keyScriptProcessed); 277 | }); 278 | app.get('/workers/:address', minerpage); 279 | app.get('/stats/:coin', poolStatPage); 280 | app.get('/:page', route); 281 | app.get('/', route); 282 | 283 | app.get('/api/:method', function (req, res, next) { 284 | portalApi.handleApiRequest(req, res, next); 285 | }); 286 | 287 | app.post('/api/admin/:method', function (req, res, next) { 288 | if (portalConfig.website 289 | && portalConfig.website.adminCenter 290 | && portalConfig.website.adminCenter.enabled) { 291 | if (portalConfig.website.adminCenter.password === req.body.password) 292 | portalApi.handleAdminApiRequest(req, res, next); 293 | else 294 | res.send(401, JSON.stringify({error: 'Incorrect Password'})); 295 | 296 | } 297 | else 298 | next(); 299 | 300 | }); 301 | 302 | app.use(compress()); 303 | app.use('/static', express.static('website/static')); 304 | 305 | app.use(function (err, req, res, next) { 306 | console.error(err.stack); 307 | res.status(500).send('Something broke!'); 308 | }); 309 | 310 | try { 311 | app.listen(portalConfig.website.port, portalConfig.website.host, function () { 312 | logger.info('Website started on %s:%s', portalConfig.website.host,portalConfig.website.port); 313 | }); 314 | } 315 | catch (e) { 316 | logger.error('e = %s', JSON.stringify(e)); 317 | logger.error('Could not start website on %s:%s - its either in use or you do not have permission', portalConfig.website.host,portalConfig.website.port); 318 | } 319 | 320 | 321 | }; 322 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/logs/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-open-mining-portal", 3 | "version": "1.0.7", 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/cryptosharks131/BootNOMP", 16 | "bugs": { 17 | "url": "https://github.com/cryptosharks131/BootNOMP/issues" 18 | }, 19 | "license": "GPL-2.0", 20 | "author": "Matthew Little", 21 | "contributors": [ 22 | "devnulled", 23 | "foxer666", 24 | "vekexasia", 25 | "TheSeven", 26 | "Kris Klosterman" 27 | ], 28 | "main": "init.js", 29 | "bin": { 30 | "block-notify": "./scripts/blockNotify.js" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/cryptosharks131/BootNOMP.git" 35 | }, 36 | "dependencies": { 37 | "async": "^2.5.0", 38 | "bignumber.js": "^6.0.0", 39 | "body-parser": "^1.18.2", 40 | "chartjs-plugin-zoom": "^0.6.3", 41 | "colors": "^1.1.2", 42 | "compression": "^1.7.1", 43 | "dateformat": "^2.2.0", 44 | "dot": "^1.1.2", 45 | "exponential-moving-average": "^1.0.0", 46 | "express": "^4.16.1", 47 | "mysql": "^2.15.0", 48 | "node-json-minify": "^1.0.0", 49 | "node-watch": "^0.5.5", 50 | "nonce": "^1.0.4", 51 | "redis": "^2.7.1", 52 | "request": "^2.83.0", 53 | "sma": "^0.1.0", 54 | "stratum-pool": "git://github.com/cryptosharks131/node-stratum-pool.git", 55 | "winston": "3.0.0-rc2", 56 | "winston-daily-rotate-file": "^3.0.0" 57 | }, 58 | "engines": { 59 | "node": ">=8.1.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pool_configs/aced.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "aced.json", 4 | "address": "POOL_ADDRESS", 5 | "rewardRecipients": { 6 | "FEE_ADDRESS": 0.5 7 | }, 8 | "paymentProcessing": { 9 | "enabled": true, 10 | "paymentInterval": 3600, 11 | "minimumPayment": 0.1, 12 | "daemon": { 13 | "host": "127.0.0.1", 14 | "port": 24127, 15 | "user": "USERNAME", 16 | "password": "PASSWORD" 17 | } 18 | }, 19 | "ports": { 20 | "4032": { 21 | "diff": 128, 22 | "varDiff": { 23 | "minDiff": 8, 24 | "maxDiff": 512, 25 | "targetTime": 15, 26 | "retargetTime": 60, 27 | "variancePercent": 30 28 | } 29 | }, 30 | "4256": { 31 | "diff": 256, 32 | "varDiff": { 33 | "minDiff": 128, 34 | "maxDiff": 512, 35 | "targetTime": 15, 36 | "retargetTime": 60, 37 | "variancePercent": 30 38 | } 39 | } 40 | }, 41 | "daemons": [ 42 | { 43 | "host": "127.0.0.1", 44 | "port": 24127, 45 | "user": "USERNAME", 46 | "password": "PASSWORD" 47 | } 48 | ], 49 | "p2p": { 50 | "enabled": false, 51 | "host": "127.0.0.1", 52 | "port": 5889, 53 | "disableTransactions": true 54 | }, 55 | "mposMode": { 56 | "enabled": false, 57 | "host": "127.0.0.1", 58 | "port": 3306, 59 | "user": "me", 60 | "password": "mypass", 61 | "database": "vtc", 62 | "checkPassword": true, 63 | "autoCreateWorker": false 64 | }, 65 | "mongoMode": { 66 | "enabled": false, 67 | "host": "127.0.0.1", 68 | "user": "", 69 | "pass": "", 70 | "database": "vtc", 71 | "authMechanism": "DEFAULT" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pool_configs/litecoin_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "litecoin.json", 4 | 5 | "address": "pool_address", 6 | 7 | "rewardRecipients": { 8 | "pool_reward_fee_address1": 1.5, 9 | "pool_reward_fee_address2": 0.1 10 | }, 11 | 12 | "paymentProcessing": { 13 | "enabled": true, 14 | "paymentInterval": 600, 15 | "minimumPayment": 0.01, 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": false, 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 | } -------------------------------------------------------------------------------- /pool_configs/methuselah.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "methuselah.json", 4 | "address": "POOL_ADDRESS", 5 | "rewardRecipients": { 6 | "FEE_ADDRESS": 0.5 7 | }, 8 | "paymentProcessing": { 9 | "enabled": true, 10 | "paymentInterval": 3600, 11 | "minimumPayment": 0.01, 12 | "daemon": { 13 | "host": "127.0.0.1", 14 | "port": 6269, 15 | "user": "USERNAME", 16 | "password": "PASSWORD" 17 | } 18 | }, 19 | "ports": { 20 | "3032": { 21 | "diff": 32, 22 | "varDiff": { 23 | "minDiff": 8, 24 | "maxDiff": 512, 25 | "targetTime": 15, 26 | "retargetTime": 60, 27 | "variancePercent": 30 28 | } 29 | }, 30 | "3256": { 31 | "diff": 256 32 | } 33 | }, 34 | "daemons": [ 35 | { 36 | "host": "127.0.0.1", 37 | "port": 6269, 38 | "user": "USERNAME", 39 | "password": "PASSWORD" 40 | } 41 | ], 42 | "p2p": { 43 | "enabled": false, 44 | "host": "127.0.0.1", 45 | "port": 5889, 46 | "disableTransactions": true 47 | }, 48 | "mposMode": { 49 | "enabled": false, 50 | "host": "127.0.0.1", 51 | "port": 3306, 52 | "user": "me", 53 | "password": "mypass", 54 | "database": "vtc", 55 | "checkPassword": true, 56 | "autoCreateWorker": false 57 | }, 58 | "mongoMode": { 59 | "enabled": false, 60 | "host": "127.0.0.1", 61 | "user": "", 62 | "pass": "", 63 | "database": "vtc", 64 | "authMechanism": "DEFAULT" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pool_configs/scribe.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "scribe.json", 4 | "address": "POOL_ADDRESS", 5 | "rewardRecipients": { 6 | "POOL_FEES": 0.5 7 | }, 8 | "paymentProcessing": { 9 | "enabled": true, 10 | "paymentInterval": 3600, 11 | "minimumPayment": 0.01, 12 | "daemon": { 13 | "host": "127.0.0.1", 14 | "port": 8801, 15 | "user": "USERNAME", 16 | "password": "PASSWORD" 17 | } 18 | }, 19 | "ports": { 20 | "3033": { 21 | "diff": 32, 22 | "varDiff": { 23 | "minDiff": 8, 24 | "maxDiff": 512, 25 | "targetTime": 15, 26 | "retargetTime": 60, 27 | "variancePercent": 30 28 | } 29 | }, 30 | "3257": { 31 | "diff": 256 32 | } 33 | }, 34 | "daemons": [ 35 | { 36 | "host": "127.0.0.1", 37 | "port": 8801, 38 | "user": "USERNAME", 39 | "password": "PASSWORD" 40 | } 41 | ], 42 | "p2p": { 43 | "enabled": false, 44 | "host": "127.0.0.1", 45 | "port": 5889, 46 | "disableTransactions": true 47 | }, 48 | "mposMode": { 49 | "enabled": false, 50 | "host": "127.0.0.1", 51 | "port": 3306, 52 | "user": "me", 53 | "password": "mypass", 54 | "database": "vtc", 55 | "checkPassword": true, 56 | "autoCreateWorker": false 57 | }, 58 | "mongoMode": { 59 | "enabled": false, 60 | "host": "127.0.0.1", 61 | "user": "", 62 | "pass": "", 63 | "database": "vtc", 64 | "authMechanism": "DEFAULT" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pool_configs/vertcoin_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "vertcoin.json", 4 | "address": "pool_address", 5 | "rewardRecipients": { 6 | "pool_reward_fee_address": 1 7 | }, 8 | "paymentProcessing": { 9 | "enabled": true, 10 | "paymentInterval": 600, 11 | "minimumPayment": 0.01, 12 | "daemon": { 13 | "host": "127.0.0.1", 14 | "port": 5888, 15 | "user": "testuser", 16 | "password": "testpass" 17 | } 18 | }, 19 | "ports": { 20 | "3008": { 21 | "diff": 8 22 | }, 23 | "3032": { 24 | "diff": 32, 25 | "varDiff": { 26 | "minDiff": 8, 27 | "maxDiff": 512, 28 | "targetTime": 15, 29 | "retargetTime": 90, 30 | "variancePercent": 30 31 | } 32 | }, 33 | "3256": { 34 | "diff": 256 35 | } 36 | }, 37 | "daemons": [ 38 | { 39 | "host": "127.0.0.1", 40 | "port": 5888, 41 | "user": "testuser", 42 | "password": "testpass" 43 | } 44 | ], 45 | "p2p": { 46 | "enabled": false, 47 | "host": "127.0.0.1", 48 | "port": 5889, 49 | "disableTransactions": true 50 | }, 51 | "mposMode": { 52 | "enabled": false, 53 | "host": "127.0.0.1", 54 | "port": 3306, 55 | "user": "me", 56 | "password": "mypass", 57 | "database": "vtc", 58 | "checkPassword": true, 59 | "autoCreateWorker": false 60 | }, 61 | "mongoMode": { 62 | "enabled": false, 63 | "host": "127.0.0.1", 64 | "user": "", 65 | "pass": "", 66 | "database": "vtc", 67 | "authMechanism": "DEFAULT" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /scripts/blocknotify: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/scripts/blocknotify -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | console.log(data.toString()); 36 | }).on('close', function () { 37 | }); -------------------------------------------------------------------------------- /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 |
-------------------------------------------------------------------------------- /website/pages/api.html: -------------------------------------------------------------------------------- 1 |
2 | API Docs here 3 | 4 |
    5 |
  • /stats - raw json statistic
  • 6 |
  • /pool_stats - historical time per pool json
  • 7 |
  • /live_stats - live stats
  • 8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /website/pages/blocks.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 | 76 | 77 | 78 | 79 |
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}}
Paid:{{=it.stats.pools[sortedPools[0]].poolStats.totalPaid}} {{=it.stats.pools[sortedPools[0]].symbol}}
80 |
81 |

Last 50 Blocks

82 |
83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
HeightTimeInfoSharesMinersPaidStatus
100 |
101 |
102 |
103 |
104 | 105 | 155 | -------------------------------------------------------------------------------- /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 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |

Welcome To Crypto Sharks Pool!

71 |

Thank you for choosing us for mining! If you like it, be sure to let your friends know about us! 72 | If you ever have any questions, suggestions, or just wanna chat, make sure to join 73 | our Discord!

74 |
75 |

June 1st, 2018: Enjoy the new updates! We have some website work left and many more great features planned! Good luck mining!

76 |
77 |
78 |
79 |
80 | 83 | -------------------------------------------------------------------------------- /website/pages/getting_started.html: -------------------------------------------------------------------------------- 1 | 28 | 29 |
30 |
31 |
32 |

New? Don't Know Where To Start?

33 |

Don't worry! We got you covered! Click below to learn more about cryptocurrency mining, and how you can get started today!

34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 |

Know What You're Doing?

42 |

Get started with our pool by using the simple configuration tool we provided below!

43 |
44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 65 | 66 | 67 | 68 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 96 | 97 | 98 | 106 | 107 | 108 |

Miner Configuration Generator

Coin

57 | 64 |

Miner

69 | 75 |

Wallet*

83 | {{ var info = []; }} 84 | {{ for(var pool in it.poolsConfigs) { 85 | info.push({ 86 | coin: it.poolsConfigs[pool].coin, 87 | algo: it.poolsConfigs[pool].coin.algorithm, 88 | ports: it.poolsConfigs[pool].ports, 89 | host: it.portalConfig.website.stratumHost 90 | }); 91 | }} 92 | {{ } }} 93 | {{ info = JSON.stringify(info).replace(/"/g, '"'); }} 94 | Generate 95 |
99 |
100 | 101 |
102 | 103 |
104 |
105 |
109 |
110 | 223 | -------------------------------------------------------------------------------- /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/pages/home.html: -------------------------------------------------------------------------------- 1 | 183 |
184 |
185 |
186 |
187 |
188 |
189 |

Welcome To Crypto Sharks Pool!

190 |

A multi-coin mining pool made by miners, for miners. No matter if you are new to mining, or experienced. Our goal is to make a fast and reliable cryptocurrency mining pool, that is easy for everyone to use!

191 |
192 | 195 | 198 |
199 | Want To Learn More? 200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |

What Our Pools Feature

212 |
213 |
214 |
215 |
216 |

They're Fast

217 |

Speed is key when it comes to mining. So we chose hardware that can provide just that! Giving our users the best connection possible!

218 |
219 |
220 |
221 |
222 |

They're Protected

223 |

Don't like downtime? Neither do we! So we take measures to protect our site from any malicious attacks that could disrupt mining!

224 |
225 |
226 |
227 |
228 |

They Have Low Fees

229 |

We're not here to make profit. So we keep our fees low! Why have any at all you may ask? We gotta pay bills for the website somehow.

230 |
231 |
232 |
233 |
234 |

It's All Easy To Use

235 |

No matter your experience, you can mine using our pool! We make make mining easy! Head over to Getting Started to learn more!

236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |

Current Global Stats

247 |
248 |
249 |
250 |
251 |

Total Miners

252 | {{ var count = 0; }} 253 | {{ for(var pool in it.stats.pools) { }} 254 | {{ count += it.stats.pools[pool].workerCount; }} 255 | {{ } }} 256 |

{{=count}}

257 |
258 |
259 |
260 |
261 |

Total Hashrate

262 | {{ var hashrate = 0; }} 263 | {{ for(var pool in it.stats.pools) { }} 264 | {{ hashrate += it.stats.pools[pool].hashrate; }} 265 | {{ } }} 266 | {{ var byteUnits = [' KH/s', ' MH/s', ' GH/s', ' TH/s', ' PH/s']; }} 267 | {{ var i = -1; }} 268 | {{ do { }} 269 | {{ hashrate = hashrate / 1024; }} 270 | {{ i++; }} 271 | {{ } while (hashrate > 1024); }} 272 |

{{=Math.round(hashrate) + byteUnits[i]}}

273 |
274 |
275 |
276 |
277 |

Algorithms Supported

278 |

{{=Object.keys(it.stats.algos).length}}

279 |
280 |
281 |
282 |
283 |

Pools Hosted

284 |

{{=Object.keys(it.stats.pools).length}}

285 |
286 |
287 |
288 |
289 |
290 |
291 | 292 | 307 | -------------------------------------------------------------------------------- /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 60 | for Nvidia GPU's, SGMiner for AMD GPU's, CPU-OPT Miner 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 |

Don't worry! Join our Discord channel for more assistance!

90 |
91 |
92 |
93 | 96 | -------------------------------------------------------------------------------- /website/pages/miner_stats.html: -------------------------------------------------------------------------------- 1 | 70 | 71 |
72 |
73 |

Account Details

74 |
75 |
76 |

Total Paid:

77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
Worker Hashrate History
Colors are random! If you don't like the ones given, simply refresh the page!
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | 103 | 114 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | {{ for(var pool in it.poolsConfigs) { }} 33 | {{ if(pool !== it.stats.coin) continue; }} 34 | 35 | {{ break; }} 36 | {{ } }} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {{ break; }} 61 | {{ } }} 62 | 63 |
General InformationPool InformationNetwork Information
Coin: Port:{{=Object.keys(it.poolsConfigs[pool].ports)}}Hashrate:{{=it.stats.pools[pool].hashrateString}}Pending:{{=it.stats.pools[pool].blocks.pending}}Connections:{{=it.stats.pools[pool].poolStats.networkConnections}}Difficulty:{{=Number(Math.round(it.stats.pools[pool].poolStats.networkDiff + 'e' + 4) + 'e-' + 4)}}
Name: {{=(String(it.stats.coin).charAt(0).toUpperCase() + String(it.stats.coin).slice(1))}}Workers:{{=it.stats.pools[pool].workerCount}}Valid Shares:{{=it.stats.pools[pool].poolStats.validShares}}Confirmed:{{=it.stats.pools[pool].blocks.confirmed}}Hashrate:{{=it.stats.pools[pool].poolStats.networkSolsString}}Height:{{=it.stats.pools[pool].poolStats.networkBlocks}}
64 |
65 |
66 |
67 |
68 |
Hashrate History
69 |
70 |
71 |
72 |
73 |
74 |
Worker History
75 |
76 |
77 |
78 |
79 |
80 |
Pending Block History
81 |
82 |
83 |
84 |
85 | 86 | 87 | 88 | 98 | -------------------------------------------------------------------------------- /website/pages/pools.html: -------------------------------------------------------------------------------- 1 |
2 |

Quick Start Commands (GPU Algorithms):

3 |
4 | ccminer -a <ALGORITHM> -o stratum+tcp://us1.cryptosharkspool.com:<PORT> -u <WALLET.OPTIONAL_WORKER> -p <ANYTHING> 5 |
6 |
7 | sgminer -k <ALGORITHM> -o stratum+tcp://us1.cryptosharkspool.com:<PORT> -u <WALLET.OPTIONAL_WORKER> -p <ANYTHING> 8 |
9 |

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

10 |
11 |
12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{ for(var pool in it.stats.pools) { }} 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{ var total = 0.0; }} 33 | {{ var rewardRecipients = it.stats.pools[pool].rewardRecipients || {}; }} 34 | {{ for (var r in rewardRecipients) { }} 35 | {{ total += rewardRecipients[r]; }} 36 | {{ } }} 37 | 38 | 39 | {{ } }} 40 | 41 |
Port 16 | SymbolCoinAlgorithmWorkersHash RateFee
{{=Object.keys(it.poolsConfigs[pool].ports)[0]}}{{=it.stats.pools[pool].symbol}}{{=it.stats.pools[pool].name.charAt(0).toUpperCase() + it.stats.pools[pool].name.slice(1)}}{{=it.stats.pools[pool].algorithm}}{{=it.stats.pools[pool].workerCount}}{{=it.stats.pools[pool].hashrateString}}{{=total}}%
42 |
43 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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": 5, 10 | "bLengthChange": false, 11 | "iDisplayLength": 5 12 | }); 13 | var cachedWallets = Cookies.get('wallets'); 14 | if(cachedWallets && cachedWallets.length > 0){ 15 | cachedWallets = JSON.parse(cachedWallets); 16 | for(w in cachedWallets) { 17 | var wallet = cachedWallets[w].split(','); 18 | var coin = wallet[0]; 19 | var address = wallet[1]; 20 | dataTable.row.add([ 21 | " " + address + "", 22 | "" 23 | ]).draw(false); 24 | $('#' + address).click(function(event) { 25 | if(confirm("Are you sure you want to delete address: " + address)){ 26 | cachedWallets.splice(w, 1); 27 | Cookies.remove('wallets'); 28 | Cookies.set('wallets', cachedWallets, { expires: 30 }); 29 | location.reload(); 30 | } 31 | }); 32 | } 33 | } 34 | //binds the myFormOnSubmit method below to run as part of your form's onsubmit method 35 | $('#searchButton').click(myFormOnSubmit); 36 | 37 | //runs when the form is trying to submit 38 | function myFormOnSubmit(event) { 39 | var f = $(this); 40 | // note, you have to match on attribute selectors 41 | // you may want to give each of these fields an id=".." attribute as well to select against #IdName 42 | var search = $('#searchBar').val(); 43 | var isValid = false; 44 | 45 | var coin = ""; 46 | var wallets = Cookies.get('wallets'); 47 | var stored = false; 48 | if(wallets) { 49 | wallets = JSON.parse(wallets); 50 | for(w in wallets) { 51 | if(wallets[w].split(',')[1] === search) { 52 | stored = true; 53 | break; 54 | } 55 | } 56 | } 57 | if(stored){ 58 | alert('Address Already Stored!'); 59 | event.preventDefault(); //stop submit 60 | return; 61 | } 62 | if(!wallets){ 63 | wallets = []; 64 | } 65 | $.each(statData.pools, function(i, v) { 66 | if(!isValid){ 67 | for(worker in v.workers){ 68 | worker = worker.split('.')[0]; 69 | if(worker === search){ 70 | isValid = true; 71 | wallets.push(String(i + ',' + worker)); 72 | break; 73 | } 74 | } 75 | } 76 | }); 77 | if (!isValid) { 78 | alert('No Address Found!'); 79 | event.preventDefault(); //stop submit 80 | return; 81 | } else { 82 | Cookies.remove('wallets'); 83 | Cookies.set('wallets', wallets, { expires: 30 }); 84 | } 85 | } 86 | }); 87 | 88 | 89 | 90 | $.getJSON('/api/stats', function(data) { 91 | statData = data; 92 | }); 93 | -------------------------------------------------------------------------------- /website/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/favicon.png -------------------------------------------------------------------------------- /website/static/icons/aced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/aced.png -------------------------------------------------------------------------------- /website/static/icons/dixicoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/dixicoin.png -------------------------------------------------------------------------------- /website/static/icons/methuselah.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/methuselah.png -------------------------------------------------------------------------------- /website/static/icons/qbiccoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/qbiccoin.png -------------------------------------------------------------------------------- /website/static/icons/qudex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/qudex.png -------------------------------------------------------------------------------- /website/static/icons/ravencoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/ravencoin.png -------------------------------------------------------------------------------- /website/static/icons/scribe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/scribe.png -------------------------------------------------------------------------------- /website/static/icons/wavicoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/icons/wavicoin.png -------------------------------------------------------------------------------- /website/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/img/favicon.png -------------------------------------------------------------------------------- /website/static/img/my-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/img/my-banner.png -------------------------------------------------------------------------------- /website/static/img/noto-hash-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosharks131/NiceNOMP/15fde8b7a3acc2088248aa2ad2f62c9500286155/website/static/img/noto-hash-banner.png -------------------------------------------------------------------------------- /website/static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | pointerOptions: { 40 | toplength: -15, 41 | bottomlength: 10, 42 | bottomwidth: 12, 43 | color: '#8e8e93', 44 | stroke: '#ffffff', 45 | stroke_width: 3, 46 | stroke_linecap: 'round' 47 | }, 48 | title: "Shares This Round", 49 | gaugeWidthScale: 0.6, 50 | levelColors:["#e8e84c", "#6cdb5e"] 51 | }); 52 | invalidGage = new JustGage({ 53 | id: "validShare", 54 | value: Math.min(stats.invalidShares, 100), 55 | min: 0, 56 | max: 100, 57 | symbol: '%', 58 | pointer: true, 59 | pointerOptions: { 60 | toplength: -15, 61 | bottomlength: 10, 62 | bottomwidth: 12, 63 | color: '#8e8e93', 64 | stroke: '#ffffff', 65 | stroke_width: 3, 66 | stroke_linecap: 'round' 67 | }, 68 | title: "Recent Invalid Shares", 69 | gaugeWidthScale: 0.6, 70 | levelColors:["#e8e84c", "#f73d3d"] 71 | }); 72 | workerGage= new JustGage({ 73 | id: "workerDominance", 74 | value: stats.miners ? (Object.keys(stats.miners).length / stats.poolSize) * 100 : 0, 75 | min: 0, 76 | max: 100, 77 | symbol: '%', 78 | pointer: true, 79 | pointerOptions: { 80 | toplength: -15, 81 | bottomlength: 10, 82 | bottomwidth: 12, 83 | color: '#8e8e93', 84 | stroke: '#ffffff', 85 | stroke_width: 3, 86 | stroke_linecap: 'round' 87 | }, 88 | title: "Worker Dominance", 89 | gaugeWidthScale: 0.6, 90 | levelColors:["#e8e84c", "#6cdb5e"] 91 | }); 92 | var high = 0; 93 | console.log(stats.hashrate); 94 | hashGage = new JustGage({ 95 | id: "hashDominance", 96 | value: stats.hashrate > 0 ? (stats.hashrate / stats.poolHashrate) * 100 : 0, 97 | min: 0, 98 | max: 100, 99 | symbol: '%', 100 | title: "Hashrate Dominance", 101 | levelColors:["#e8e84c", "#6cdb5e"], 102 | pointer: true, 103 | pointerOptions: { 104 | toplength: -15, 105 | bottomlength: 10, 106 | bottomwidth: 12, 107 | color: '#8e8e93', 108 | stroke: '#ffffff', 109 | stroke_width: 3, 110 | stroke_linecap: 'round' 111 | }, 112 | gaugeWidthScale: 0.6 113 | }); 114 | var maxScale = 0; 115 | var label = 'H/s'; 116 | for (var w in stats.miners) { 117 | var pair = getReadableHashRatePair(Math.max.apply(null, stats.miners[w].hashrate.map(x => x[1]))); 118 | var i = pair[2]; 119 | if (maxScale < i) { 120 | maxScale = i; 121 | label = pair[1]; 122 | } 123 | } 124 | var dataset = []; 125 | for (var d in stats.miners) { 126 | var data = stats.miners[d]; 127 | var color = getRandomPastelColor(); 128 | var o = { 129 | label: data.key, 130 | fill: false, 131 | data: data.hashrate.map(x => { 132 | return { 133 | t: x[0], 134 | y: getScaledHashrate(x[1], i) 135 | } 136 | }), 137 | borderWidth: 2, 138 | backgroundColor: color, 139 | borderColor: color 140 | }; 141 | dataset.push(o); 142 | } 143 | 144 | workerHashrateChart = createDefaultLineChart( 145 | document.getElementById("workerHashChart").getContext('2d'), 146 | dataset, 147 | 'Time', 148 | label 149 | ); 150 | } 151 | 152 | function updateStats() { 153 | var stats = getWorkerStats(_miner); 154 | totalHash = stats.hashrate; 155 | totalPaid = stats.paid; 156 | totalBal = stats.balance; 157 | totalImmature = stats.immature; 158 | totalShares = stats.totalShares; 159 | // update miner stats 160 | $("#statsHashrate").text(getReadableHashRateString(totalHash)); 161 | $("#statsHashrateAvg").text(getReadableHashRateString(calculateAverageHashrate(null))); 162 | $("#statsTotalImmature").text(totalImmature); 163 | $("#statsTotalBal").text(totalBal); 164 | $("#statsTotalPaid").text(totalPaid); 165 | } 166 | 167 | function updateWorkerStats() { 168 | var stats = getWorkerStats(_miner); 169 | // update worker stats 170 | var i = 0; 171 | for (var w in stats.miners) { 172 | i++; 173 | var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, ''); 174 | var saneWorkerName = getWorkerNameFromAddress(w); 175 | console.log(stats.miners[w]); 176 | $("#statsHashrate" + htmlSafeWorkerName).text(getReadableHashRateString(stats.miners[w].hashrate[stats.miners[w].hashrate.length - 1] || 0)); 177 | $("#statsHashrateAvg" + htmlSafeWorkerName).text(getReadableHashRateString(calculateAverageHashrate(saneWorkerName))); 178 | } 179 | } 180 | 181 | function addWorkerToDisplay(name, htmlSafeName, workerObj) { 182 | var htmlToAdd = ""; 183 | htmlToAdd = '
    '; 184 | htmlToAdd += '
    ' + name.replace(/[^\w\s]/gi, '') + '
    '; 185 | htmlToAdd += '
    ' + getReadableHashRateString(workerObj.hashrate[workerObj.hashrate.length - 1][1] || 0) + ' (Now)
    '; 186 | htmlToAdd += '
    ' + getReadableHashRateString(calculateAverageHashrate(name)) + ' (Avg)
    '; 187 | htmlToAdd += '
    '; 188 | $("#boxesWorkers").html($("#boxesWorkers").html() + htmlToAdd); 189 | } 190 | 191 | function calculateAverageHashrate(worker) { 192 | var stats = getWorkerStats(_miner); 193 | var count = 0; 194 | var total = 1; 195 | var avg = 0; 196 | for (w in stats.miners) { 197 | count = 0; 198 | for (var ii = 0; ii < stats.miners[w].hashrate.length; ii++) { 199 | if (worker == null || stats.miners[w].key === worker) { 200 | count++; 201 | avg += parseFloat(stats.miners[w].hashrate[ii][1]); 202 | } 203 | } 204 | if (count > total) 205 | total = count; 206 | } 207 | avg = avg / total; 208 | return avg; 209 | } 210 | 211 | 212 | function rebuildWorkerDisplay() { 213 | var stats = getWorkerStats(_miner); 214 | $("#boxesWorkers").html(""); 215 | var i = 0; 216 | for (var w in stats.miners) { 217 | i++; 218 | var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, ''); 219 | var saneWorkerName = getWorkerNameFromAddress(w); 220 | addWorkerToDisplay(saneWorkerName, htmlSafeWorkerName, stats.miners[w]); 221 | } 222 | } 223 | 224 | 225 | // grab initial stats 226 | $.getJSON('/api/worker_stats?' + _miner, function(data) { 227 | if (document.hidden) return; 228 | $.getJSON('/api/pool_stats', function(statData) { 229 | addWorkerToTracker(statData, data, _miner, function(){ 230 | var stats = getWorkerStats(_miner); 231 | statData = data; 232 | for (var w in statData.workers) { 233 | _workerCount++; 234 | } 235 | displayCharts(); 236 | rebuildWorkerDisplay(); 237 | updateStats(); 238 | 239 | $('#total-paid-label').append(stats.paid.toFixed(8) + ' ' + stats.symbol); 240 | }); 241 | }); 242 | }); 243 | 244 | 245 | // live stat updates 246 | statsSource.addEventListener('message', function(e) { 247 | var stats = JSON.parse(e.data); 248 | $.getJSON('/api/worker_stats?' + _miner, function(data) { 249 | //$('#total-paid-label').empty(); 250 | //$('#total-paid-label').append(total.toFixed(8) + ' ' + symbol); 251 | }); 252 | }); 253 | -------------------------------------------------------------------------------- /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} -------------------------------------------------------------------------------- /website/static/pool_stats.js: -------------------------------------------------------------------------------- 1 | var poolWorkerChart; 2 | var poolHashrateChart; 3 | var poolBlockChart; 4 | 5 | function displayCharts() { 6 | var stats = getPoolStats(poolName); 7 | var maxScale = getReadableHashRatePair(Math.max.apply(null, stats.hashrate.map(x => 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/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/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 | statsSource.addEventListener('message', function(e) { 172 | var stats = JSON.parse(e.data); 173 | statData.push(stats); 174 | 175 | var newPoolAdded = (function() { 176 | for (var p in stats.pools) { 177 | if (poolKeys.indexOf(p) === -1) 178 | return true; 179 | } 180 | return false; 181 | })(); 182 | 183 | if (newPoolAdded || Object.keys(stats.pools).length > poolKeys.length) { 184 | buildChartData(); 185 | displayCharts(); 186 | } else { 187 | var time = stats.time * 1000; 188 | for (var f = 0; f < poolKeys.length; f++) { 189 | var pool = poolKeys[f]; 190 | for (var i = 0; i < poolWorkerData.length; i++) { 191 | if (poolWorkerData[i].key === pool) { 192 | poolWorkerData[i].values.shift(); 193 | poolWorkerData[i].values.push([time, pool in stats.pools ? stats.pools[pool].workerCount : 0]); 194 | break; 195 | } 196 | } 197 | for (var i = 0; i < poolHashrateData.length; i++) { 198 | if (poolHashrateData[i].key === pool) { 199 | poolHashrateData[i].values.shift(); 200 | poolHashrateData[i].values.push([time, pool in stats.pools ? stats.pools[pool].hashrate : 0]); 201 | break; 202 | } 203 | } 204 | for (var i = 0; i < poolBlockData.length; i++) { 205 | if (poolBlockData[i].key === pool) { 206 | poolBlockData[i].values.shift(); 207 | poolBlockData[i].values.push([time, pool in stats.pools ? stats.pools[pool].blocks.pending : 0]); 208 | break; 209 | } 210 | } 211 | } 212 | TriggerChartUpdates(); 213 | } 214 | 215 | 216 | }); 217 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------