37 |
38 | {{ for(var pool in it.stats.pools) { }}
39 |
40 |
41 |
{{=it.stats.pools[pool].name}}
42 |
43 |
44 |
45 |
46 | Address
47 | Shares
48 | Invalid shares
49 | Efficiency
50 | Hashrate
51 |
52 |
53 | {{ for(var worker in it.stats.pools[pool].workers) { }}
54 |
55 | {{ let workerInfo = worker.split('.'); }}
56 | {{ if (workerInfo.length === 2) { }}
57 | {{ let worker = workerInfo[0]; }}
58 | {{ } }}
59 |
60 | {{var workerstat = it.stats.pools[pool].workers[worker];}}
61 |
62 |
63 |
64 |
65 | {{=worker}}
66 | {{=Math.floor(workerstat.shares)}}
67 | {{=Math.floor(workerstat.invalidshares)}}
68 | {{? workerstat.shares > 0}} {{=Math.floor(10000 * workerstat.shares / (workerstat.shares + workerstat.invalidshares)) / 100}}% {{??}} 0% {{?}}
69 | {{=workerstat.hashrateString}}
70 |
71 | {{ } }}
72 |
73 |
74 |
75 |
76 | {{ } }}
77 |
--------------------------------------------------------------------------------
/pool_configs/pexacoin_example.json:
--------------------------------------------------------------------------------
1 | {
2 | "enabled": false,
3 | "coin": "pexacoin.json",
4 |
5 | "address": "YOUR_POOLS_WALLET_ADDRESS",
6 |
7 | "donateaddress": "XF5zeEsu63n32xT4duAG853Wtk1o6tbk7v",
8 |
9 | "rewardRecipients": {
10 | "YOUR_FEE1_WALLET_ADDRESS": 1.5,
11 | "XF5zeEsu63n32xT4duAG853Wtk1o6tbk7v": 0.1
12 | },
13 |
14 | "paymentProcessing": {
15 | "enabled": true,
16 | "schema": "PROP",
17 | "paymentInterval": 300,
18 | "minimumPayment": 0.25,
19 | "maxBlocksPerPayment": 10,
20 | "minConf": 30,
21 | "coinPrecision": 8,
22 | "daemon": {
23 | "host": "127.0.0.1",
24 | "port": 6005,
25 | "user": "MY_RPC_USER",
26 | "password": "MY_RPC_PASS"
27 | }
28 | },
29 |
30 | "ports": {
31 | "3008": {
32 | "varDiff": {
33 | "minDiff": 0,
34 | "maxDiff": 15,
35 | "targetTime": 10,
36 | "retargetTime": 60,
37 | "variancePercent": 30,
38 | "maxJump": 25
39 | }
40 | },
41 | "3032": {
42 | "varDiff": {
43 | "minDiff": 0,
44 | "maxDiff": 600,
45 | "targetTime": 10,
46 | "retargetTime": 60,
47 | "variancePercent": 30,
48 | "maxJump": 50
49 | }
50 | },
51 | "3256": {
52 | "varDiff": {
53 | "minDiff": 1000,
54 | "maxDiff": 7000,
55 | "targetTime": 10,
56 | "retargetTime": 60,
57 | "variancePercent": 30,
58 | "maxJump": 50
59 | }
60 | }
61 | },
62 |
63 | "daemons": [
64 | {
65 | "host": "127.0.0.1",
66 | "port": 6005,
67 | "user": "MY_RPC_USER",
68 | "password": "MY_RPC_PASS"
69 | }
70 | ],
71 |
72 | "p2p": {
73 | "enabled": false,
74 | "host": "127.0.0.1",
75 | "port": 19333,
76 | "disableTransactions": true
77 | },
78 |
79 | "mposMode": {
80 | "enabled": false,
81 | "host": "127.0.0.1",
82 | "port": 3306,
83 | "user": "me",
84 | "password": "mypass",
85 | "database": "ltc",
86 | "checkPassword": true,
87 | "autoCreateWorker": false
88 | },
89 |
90 | "mongoMode": {
91 | "enabled": false,
92 | "host": "127.0.0.1",
93 | "user": "",
94 | "pass": "",
95 | "database": "ltc",
96 | "authMechanism": "DEFAULT"
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/pool_configs/placeholder_example.json:
--------------------------------------------------------------------------------
1 | {
2 | "enabled": false,
3 | "coin": "placeholder.json",
4 |
5 | "address": "YOUR_POOLS_WALLET_ADDRESS",
6 |
7 | "donateaddress": "FD2RVJBxwBvNLnN4EzHpHNTho2PQyDLeAt",
8 |
9 | "rewardRecipients": {
10 | "YOUR_FEE1_WALLET_ADDRESS": 1.5,
11 | "FD2RVJBxwBvNLnN4EzHpHNTho2PQyDLeAt": 0.1
12 | },
13 |
14 | "paymentProcessing": {
15 | "enabled": true,
16 | "schema": "PROP",
17 | "paymentInterval": 300,
18 | "minimumPayment": 0.25,
19 | "maxBlocksPerPayment": 10,
20 | "minConf": 30,
21 | "coinPrecision": 8,
22 | "daemon": {
23 | "host": "127.0.0.1",
24 | "port": 2200,
25 | "user": "MY_RPC_USER",
26 | "password": "MY_RPC_PASS"
27 | }
28 | },
29 |
30 | "ports": {
31 | "4008": {
32 | "varDiff": {
33 | "minDiff": 0,
34 | "maxDiff": 15,
35 | "targetTime": 10,
36 | "retargetTime": 60,
37 | "variancePercent": 30,
38 | "maxJump": 25
39 | }
40 | },
41 | "4032": {
42 | "varDiff": {
43 | "minDiff": 0,
44 | "maxDiff": 600,
45 | "targetTime": 10,
46 | "retargetTime": 60,
47 | "variancePercent": 30,
48 | "maxJump": 50
49 | }
50 | },
51 | "4256": {
52 | "varDiff": {
53 | "minDiff": 1000,
54 | "maxDiff": 7000,
55 | "targetTime": 10,
56 | "retargetTime": 60,
57 | "variancePercent": 30,
58 | "maxJump": 50
59 | }
60 | }
61 | },
62 |
63 | "daemons": [
64 | {
65 | "host": "192.168.1.20",
66 | "port": 2200,
67 | "user": "MY_RPC_USER",
68 | "password": "MY_RPC_PASS"
69 | }
70 | ],
71 |
72 | "p2p": {
73 | "enabled": false,
74 | "host": "127.0.0.1",
75 | "port": 19333,
76 | "disableTransactions": true
77 | },
78 |
79 | "mposMode": {
80 | "enabled": false,
81 | "host": "127.0.0.1",
82 | "port": 3306,
83 | "user": "me",
84 | "password": "mypass",
85 | "database": "ltc",
86 | "checkPassword": true,
87 | "autoCreateWorker": false
88 | },
89 |
90 | "mongoMode": {
91 | "enabled": false,
92 | "host": "127.0.0.1",
93 | "user": "",
94 | "pass": "",
95 | "database": "ltc",
96 | "authMechanism": "DEFAULT"
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/pool_configs/tragocoin_example.json:
--------------------------------------------------------------------------------
1 | {
2 | "enabled": false,
3 | "coin": "tragocoin.json",
4 |
5 | "address": "YOUR_POOLS_WALLET_ADDRESS",
6 |
7 | "donateaddress": "Ta26x9axaDQWaV2bt2z8Dk3R3dN7gHw9b6",
8 |
9 | "rewardRecipients": {
10 | "YOUR_FEE1_WALLET_ADDRESS": 1.5,
11 | "Ta26x9axaDQWaV2bt2z8Dk3R3dN7gHw9b6": 0.1
12 | },
13 |
14 | "paymentProcessing": {
15 | "enabled": true,
16 | "schema": "PROP",
17 | "paymentInterval": 300,
18 | "minimumPayment": 0.25,
19 | "maxBlocksPerPayment": 10,
20 | "minConf": 30,
21 | "coinPrecision": 8,
22 | "daemon": {
23 | "host": "192.168.1.20",
24 | "port": 2205,
25 | "user": "MY_RPC_USER",
26 | "password": "MY_RPC_PASS"
27 | }
28 | },
29 |
30 | "ports": {
31 | "10008": {
32 | "varDiff": {
33 | "minDiff": 0,
34 | "maxDiff": 15,
35 | "targetTime": 10,
36 | "retargetTime": 60,
37 | "variancePercent": 30,
38 | "maxJump": 25
39 | }
40 | },
41 | "10032": {
42 | "varDiff": {
43 | "minDiff": 0,
44 | "maxDiff": 600,
45 | "targetTime": 10,
46 | "retargetTime": 60,
47 | "variancePercent": 30,
48 | "maxJump": 50
49 | }
50 | },
51 | "10256": {
52 | "varDiff": {
53 | "minDiff": 1000,
54 | "maxDiff": 9000,
55 | "targetTime": 10,
56 | "retargetTime": 60,
57 | "variancePercent": 30,
58 | "maxJump": 50
59 | }
60 | }
61 | },
62 |
63 | "daemons": [
64 | {
65 | "host": "192.168.1.20",
66 | "port": 2205,
67 | "user": "MY_RPC_USER",
68 | "password": "MY_RPC_PASS"
69 | }
70 | ],
71 |
72 | "p2p": {
73 | "enabled": false,
74 | "host": "127.0.0.1",
75 | "port": 19333,
76 | "disableTransactions": true
77 | },
78 |
79 | "mposMode": {
80 | "enabled": false,
81 | "host": "127.0.0.1",
82 | "port": 3306,
83 | "user": "me",
84 | "password": "mypass",
85 | "database": "ltc",
86 | "checkPassword": true,
87 | "autoCreateWorker": false
88 | },
89 |
90 | "mongoMode": {
91 | "enabled": false,
92 | "host": "127.0.0.1",
93 | "user": "",
94 | "pass": "",
95 | "database": "ltc",
96 | "authMechanism": "DEFAULT"
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/docs/TODO.md:
--------------------------------------------------------------------------------
1 | ## Todo list
2 |
3 | -------
4 |
5 | **NOTE:** Ordered by priority
6 | **NOTE:** Order messed up... More of list of ideas.
7 |
8 | **[ ?=question ] [ !=important ] [ I=inprogress ] [ T=testing ] [ X=completed ]**
9 |
10 | || ToDo Item Description |
11 | | ------------- | ------------- |
12 | | **(!IT)** | Install script |
13 | | **(!)** | Add node-merged-pool: https://github.com/UNOMP/node-merged-pool |
14 | | **(!)** | Auto-Detect if walletd is out of date and warn admin |
15 | | **(!)** | Installer script |
16 | | **(!IT)** | Block Explorer |
17 | | **(!IT)** | * Show blocks from all coins |
18 | | **(!)** | * Immature blocks / Confirmation count |
19 | | **(X)** | Payment Explorer |
20 | | **(X)** | * Show Payment from all coins |
21 | |||
22 | | **(!)** | More statistics |
23 | | **(!)** | * BTC Value of block at time of mining |
24 | | **( )** | * Average blocks per day, week, month for each coin |
25 | | **( )** | * Average time to find block |
26 | |||
27 | | **( )** | Pools page (pools.html) needs sortable datatable [ [LeshaCat](https://github.com/leshacat) ] |
28 | | **(!I)** | Payments Module |
29 | | **(!)** | * Rewrite payments module + tests [ [LeshaCat:](https://github.com/leshacat) Was looking at this module... (rewrite, add features, still pondering) ] |
30 | | **(X)** | * Fix batch payments bug from issue https://github.com/foxer666/node-open-mining-portal/issues/106 |
31 | |||
32 | | **(!)** | Add option to make an manual payments rather than automatic (for emergency payments & pools with big network diff) [ [LeshaCat:](https://github.com/leshacat) Will put control in Admin Panel that uses RPC call ] |
33 | | **( )** | New frontend [ Are you changing the design? ] |
34 | | **( )** | Move modules in one project |
35 | | **(?)** | Write different log level at one time [ What do you mean...? ] |
36 | | **(IT)** | Docker Image / Documentation [ [LeshaCat](https://github.com/leshacat) ] |
37 | | **(IT)** | Documentation [ [LeshaCat](https://github.com/leshacat) ] |
38 | | **(!)** | MySQL/MariaDB/MongoDB options (one or all) as replacement for Redis ? |
39 | | **( )** | Auto-Detect daemon.conf settings (if in default location) [ [LeshaCat](https://github.com/leshacat) ] |
40 |
41 | ------
42 |
43 | Mark your name beside tasks you wish to work on, or are currently working on :)
44 |
45 | If you are OK with it I can knock a bit off this list, and you could handle graphics/stats/modules/redis/init.js and code I can't figure out
46 |
47 | Also I don't care to learn Redis, so adding new statistics or database options you will need to do.
48 |
49 | I have many other ideas but not going to list here.
50 |
51 | LeshaCat CURRENT Main focus: Connect string features c=PEXA, d=30, m=solo, etc. Payment processor, API, Logging
52 |
53 | ***EOF***
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "EasyNOMP",
3 | "version": "1.1.4",
4 | "description": "An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool",
5 | "keywords": [
6 | "stratum",
7 | "mining",
8 | "pool",
9 | "server",
10 | "poolserver",
11 | "bitcoin",
12 | "litecoin",
13 | "scrypt"
14 | ],
15 | "homepage": "https://github.com/EasyX-Community/EasyNOMP",
16 | "bugs": {
17 | "url": "https://github.com/EasyX-Community/EasyNOMP/issues"
18 | },
19 | "license": "GPL-2.0",
20 | "author": "Leshacat",
21 | "contributors": [
22 | "leshacat",
23 | "devnulled",
24 | "foxer666",
25 | "vekexasia",
26 | "TheSeven",
27 | "Kris Klosterman"
28 | ],
29 | "main": "init.js",
30 | "bin": {
31 | "block-notify": "./scripts/blockNotify.js"
32 | },
33 | "repository": {
34 | "type": "git",
35 | "url": "https://github.com/EasyX-Community/EasyNOMP.git"
36 | },
37 | "dependencies": {
38 | "3.npm": "^1.0.0",
39 | "asn1": "^0.2.4",
40 | "async": "^2.6.1",
41 | "bcrypt-pbkdf": "^1.0.2",
42 | "bignum": "^0.13.0",
43 | "bignumber.js": "^6.0.0",
44 | "body-parser": "^1.18.3",
45 | "chart.js": "^2.7.3",
46 | "chartjs-plugin-zoom": "^0.6.6",
47 | "cluster": "^0.7.7",
48 | "color-name": "^1.1.4",
49 | "colors": "^1.3.3",
50 | "combined-stream": "^1.0.7",
51 | "compression": "^1.7.3",
52 | "dateformat": "^2.2.0",
53 | "dot": "^1.1.2",
54 | "ecc-jsbn": "^0.1.2",
55 | "exponential-moving-average": "^1.0.0",
56 | "express": "^4.16.4",
57 | "form-data": "^2.3.3",
58 | "http-errors": "^1.7.1",
59 | "ip-anonymize": "^0.1.0",
60 | "ls": "^0.2.1",
61 | "mime-types": "^2.1.21",
62 | "moment": "^2.23.0",
63 | "multi-hashing": "git://github.com/EasyX-Community/node-multi-hashing.git",
64 | "mysql": "^2.16.0",
65 | "newrelic": "^5.1.0",
66 | "node-json-minify": "^1.0.0",
67 | "node-multi-hashing": "0.0.1",
68 | "node-watch": "^0.5.9",
69 | "nonce": "^1.0.4",
70 | "redis": "^2.7.1",
71 | "redis-commands": "^1.4.0",
72 | "request": "^2.88.0",
73 | "safe-buffer": "^5.1.2",
74 | "semver": "^5.7.1",
75 | "sma": "^0.1.1",
76 | "sshpk": "^1.15.2",
77 | "stratum-pool": "git://github.com/EasyX-Community/node-stratum-pool.git",
78 | "winston": "^3.1.0",
79 | "winston-daily-rotate-file": "^3.5.1",
80 | "winston-transport": "^4.3.0"
81 | },
82 | "engines": {
83 | "node": ">=8.1.4"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/docs/INSTALL.md:
--------------------------------------------------------------------------------
1 | ## Pool Installation Instructions
2 |
3 | -------
4 |
5 | ### Installer Script
6 | You know we like things easy, so we've created this handy installer script for you!
7 | Please be aware, this script is not yet tested, and may not even work!
8 | Simply create a user for the pool to run as, and log in to that user.
9 | Run this command
10 |
11 | ```
12 | # Please be careful, this script may or may not work :P
13 | # Manual install preferable.
14 | #
15 | git clone https://github.com/EasyX-Community/EasyNOMP.git ; cd EasyNOMP ; ./install.sh ;
16 | ```
17 | **Done!**
18 |
19 | -------
20 | ### Install Requirements
21 | ```
22 | sudo apt-get update
23 | sudo apt-get upgrade -y
24 | sudo apt-get dist-upgrade -y
25 | sudo apt-get install -y sudo git nano wget curl ntp build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev git npm nodejs nodejs-legacy libminiupnpc-dev redis-server software-properties-common fail2ban
26 | sudo add-apt-repository ppa:bitcoin/bitcoin
27 | sudo apt-get update
28 | sudo apt-get install libdb4.8-dev libdb4.8++-dev
29 | sudo systemctl enable fail2ban
30 | sudo systemctl start fail2ban
31 | sudo systemctl enable redis-server
32 | sudo systemctl start redis-server
33 | sudo systemctl enable ntp
34 | sudo systemctl start ntp
35 | wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
36 | source ~/.bashrc
37 | nvm install 8
38 | nvm use 8
39 | npm update -g
40 | npm install -g pm2@latest
41 | npm install -g npm@latest
42 | ```
43 |
44 | -------
45 | ### Flush Redis Data
46 | **If you are switching from z-nomp, your old statistics and payouts data is incompatable - to wipe it please run:**
47 | ```
48 | redis-cli FLUSHALL
49 | ```
50 | **WARNING: You will loose statistics and payout data.**
51 |
52 | -------
53 | ### Install Pool
54 | ```
55 | git clone https://github.com/EasyX-Community/EasyNOMP.git
56 | cd EasyNOMP
57 | npm install
58 | npm update
59 | npm audit fix
60 | ./pool-start.sh
61 | ```
62 |
63 | -------
64 | ### Watching Pool Logs
65 | ```
66 | ./pool-logs-watch.sh # Watch regular logs
67 |
68 | ./pool-logs-watch.sh " BTCADDRESSTOSCANFOR " # Scan for a coin address
69 |
70 | ./pool-logs-watch.sh "block>accepted>" # Scan for accepted blocks
71 |
72 | ./pool-logs-watch.sh "block>rejected>" # Scan for a rejected blocks
73 |
74 | ./pool-logs-watch.sh "share>accepted>" # Scan for accepted shares
75 |
76 | ./pool-logs-watch.sh "share>rejected>" # Scan for rejected shares
77 |
78 | ./pool-logs-watch.sh "DIFFICULTY>" # Scan for difficulty updates
79 | ```
80 |
81 | -------
82 | ### Restarting Pool
83 | ```
84 | ./pool-restart
85 | ```
86 |
87 | -------
88 | ### Startup on Boot
89 | ```
90 | pm2 startup
91 | ```
92 | Copy & paste the command (if it asks you to)
93 | Save the process list
94 | ```
95 | pm2 save
96 | ```
97 |
98 | -------
99 | ### Update Pool Source (should be done monthly at minimum)
100 | ```
101 | cd EasyNOMP
102 | git pull
103 | rm -rf node_modules
104 | npm update -g
105 | npm update -g npm
106 | npm install
107 | npm audit fix
108 | ./pool-restart.sh
109 | ```
110 |
111 | ***EOF***
112 |
--------------------------------------------------------------------------------
/libs/mongoCompatibility.js:
--------------------------------------------------------------------------------
1 | var MongoClient = require('mongodb').MongoClient,
2 | f = require('util').format;
3 |
4 | const loggerFactory = require('./logger.js');
5 |
6 | module.exports = function (poolConfig) {
7 |
8 | var mongoConfig = poolConfig.mongoMode;
9 | var coin = poolConfig.coin.name;
10 |
11 | var mongoConfig = {
12 | host: poolConfig.mongoMode.host,
13 | database: poolConfig.mongoMode.database,
14 | user: encodeURIComponent(poolConfig.mongoMode.user),
15 | pass: encodeURIComponent(poolConfig.mongoMode.pass),
16 | authMechanism: poolConfig.mongoMode.authMechanism ? poolConfig.mongoMode.authMechanism : "DEFAULT"
17 | };
18 |
19 | let logger = loggerFactory.getLogger('MongoCompatibility', coin);
20 |
21 | var logIdentify = 'MongoDB';
22 | var logComponent = coin;
23 |
24 | var connectionURL = "";
25 |
26 | if (mongoConfig.user && mongoConfig.pass) {
27 | connectionURL = f('mongodb://%s:%s@%s:27017/myproject?authMechanism=%s', mongoConfig.user, mongoConfig.pass, mongoConfig.host, mongoConfig.database, mongoConfig.authMechanism);
28 | } else {
29 | connectionURL = f('mongodb://%s:27017/%s', mongoConfig.host, mongoConfig.database);
30 | }
31 |
32 | //TODO: PRIORITY: Check to see if collection exists and create it if not
33 |
34 | var mongoInsert = function (collectionName, data, errCallback, successCallback) {
35 | MongoClient.connect(connectionURL, function (err, db) {
36 |
37 | var collection = db.collection(collectionName);
38 |
39 | collection.insert(data, function (err, result) {
40 | if (err) {
41 | errCallback(err);
42 | //TODO: do we stop it from moving on here?
43 | }
44 |
45 | successCallback(result);
46 | db.close(); //TODO: does this work? does it get called before the above callback can do whatever with result?
47 | });
48 |
49 | });
50 | };
51 |
52 | this.handleShare = function (isValidShare, isValidBlock, shareData) {
53 |
54 | var dbData = {
55 | rem_host: shareData.ip,
56 | worker: shareData.worker,
57 | valid_share: isValidShare ? 'Y' : 'N',
58 | valid_block: isValidBlock ? 'Y' : 'N',
59 | difficulty: shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1),
60 | reason: typeof(shareData.error) === 'undefined' ? null : shareData.error,
61 | solution: shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '')
62 | };
63 |
64 | mongoInsert('shares', dbData,
65 | function (err) {
66 | logger.error('Insert error when adding share: %s', JSON.stringify(err));
67 | },
68 | function (result) {
69 | logger.debug('Share inserted, result = %s', JSON.stringify(result));
70 | });
71 |
72 | };
73 |
74 | this.handleDifficultyUpdate = function (workerName, diff) {
75 | //TODO:
76 | };
77 |
78 | this.handleAuth = function (workerName, password, authCallback) {
79 | //TODO:
80 | };
81 |
82 |
83 | };
--------------------------------------------------------------------------------
/docker/config/config_example.json:
--------------------------------------------------------------------------------
1 | {
2 | "logLevel": "debug",
3 | "logColors": true,
4 |
5 | "cliHost": "127.0.0.1",
6 | "cliPort": 17117,
7 |
8 | "clustering": {
9 | "enabled": true,
10 | "forks": "auto"
11 | },
12 |
13 | "defaultPoolConfigs": {
14 | "blockRefreshInterval": 1000,
15 | "jobRebroadcastTimeout": 55,
16 | "connectionTimeout": 600,
17 | "emitInvalidBlockHashes": false,
18 | "validateWorkerUsername": true,
19 | "tcpProxyProtocol": false,
20 | "banning": {
21 | "enabled": true,
22 | "time": 600,
23 | "invalidPercent": 50,
24 | "checkThreshold": 500,
25 | "purgeInterval": 300
26 | },
27 | "redis": {
28 | "host": "127.0.0.1",
29 | "port": 6379
30 | }
31 | },
32 |
33 | "website": {
34 | "enabled": true,
35 | "host": "0.0.0.0",
36 | "port": 80,
37 | "stratumHost": "cryppit.com",
38 | "stats": {
39 | "updateInterval": 60,
40 | "historicalRetention": 43200,
41 | "hashrateWindow": 300
42 | },
43 | "adminCenter": {
44 | "enabled": true,
45 | "password": "password"
46 | }
47 | },
48 |
49 | "redis": {
50 | "host": "127.0.0.1",
51 | "port": 6379
52 | },
53 |
54 | "switching": {
55 | "switch1": {
56 | "enabled": false,
57 | "algorithm": "sha256",
58 | "ports": {
59 | "3333": {
60 | "diff": 10,
61 | "varDiff": {
62 | "minDiff": 16,
63 | "maxDiff": 512,
64 | "targetTime": 15,
65 | "retargetTime": 90,
66 | "variancePercent": 30
67 | }
68 | }
69 | }
70 | },
71 | "switch2": {
72 | "enabled": false,
73 | "algorithm": "scrypt",
74 | "ports": {
75 | "4444": {
76 | "diff": 10,
77 | "varDiff": {
78 | "minDiff": 16,
79 | "maxDiff": 512,
80 | "targetTime": 15,
81 | "retargetTime": 90,
82 | "variancePercent": 30
83 | }
84 | }
85 | }
86 | },
87 | "switch3": {
88 | "enabled": false,
89 | "algorithm": "x11",
90 | "ports": {
91 | "5555": {
92 | "diff": 0.001,
93 | "varDiff": {
94 | "minDiff": 0.001,
95 | "maxDiff": 1,
96 | "targetTime": 15,
97 | "retargetTime": 60,
98 | "variancePercent": 30
99 | }
100 | }
101 | }
102 | }
103 | },
104 |
105 | "profitSwitch": {
106 | "enabled": false,
107 | "updateInterval": 600,
108 | "depth": 0.90,
109 | "usePoloniex": true,
110 | "useCryptsy": true,
111 | "useMintpal": true,
112 | "useBittrex": true
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/website/static/dashboard.js:
--------------------------------------------------------------------------------
1 | var statData;
2 | var poolKeys;
3 |
4 | //this function is executed when the page's dom is loaded
5 | // assumes jQuery is loaded already
6 | $(function(){
7 | var dataTable = $("#walletTable").DataTable({
8 | "order": [[ 0, "desc" ]],
9 | "pageLength": 10,
10 | "bLengthChange": false,
11 | "iDisplayLength": 10,
12 | "height": 200,
13 | "pageLength": 10,
14 | "pagingType": "full_numbers",
15 | "lengthMenu": [ 25, 50, 100, 150, 300, 500 ]
16 | });
17 | var cachedWallets = Cookies.get('wallets');
18 | if(cachedWallets && cachedWallets.length > 0){
19 | cachedWallets = JSON.parse(cachedWallets);
20 | for(w in cachedWallets) {
21 | var wallet = cachedWallets[w].split(',');
22 | var coin = wallet[0];
23 | var address = wallet[1];
24 | dataTable.row.add([
25 | "
" + address + "",
26 | "
Delete "
27 | ]).draw(false);
28 | $('#' + address).click(function(event) {
29 | if(confirm("Are you sure you want to delete address: " + address)){
30 | cachedWallets.splice(w, 1);
31 | Cookies.remove('wallets');
32 | Cookies.set('wallets', cachedWallets, { expires: 30 });
33 | location.reload();
34 | }
35 | });
36 | }
37 | }
38 | //binds the myFormOnSubmit method below to run as part of your form's onsubmit method
39 | $('#searchButton').click(myFormOnSubmit);
40 |
41 | //runs when the form is trying to submit
42 | function myFormOnSubmit(event) {
43 | var f = $(this);
44 | // note, you have to match on attribute selectors
45 | // you may want to give each of these fields an id=".." attribute as well to select against #IdName
46 | var search = $('#searchBar').val();
47 | var isValid = false;
48 |
49 | var coin = "";
50 | var wallets = Cookies.get('wallets');
51 | var stored = false;
52 | if(wallets) {
53 | wallets = JSON.parse(wallets);
54 | for(w in wallets) {
55 | if(wallets[w].split(',')[1] === search) {
56 | stored = true;
57 | break;
58 | }
59 | }
60 | }
61 | if(stored){
62 | alert('Address Already Stored!');
63 | event.preventDefault(); //stop submit
64 | return;
65 | }
66 | if(!wallets){
67 | wallets = [];
68 | }
69 | $.each(statData.pools, function(i, v) {
70 | if(!isValid){
71 | for(worker in v.workers){
72 | worker = worker.split('.')[0];
73 | if(worker === search){
74 | isValid = true;
75 | wallets.push(String(i + ',' + worker));
76 | break;
77 | }
78 | }
79 | }
80 | });
81 | if (!isValid) {
82 | alert('No Address Found!');
83 | event.preventDefault(); //stop submit
84 | return;
85 | } else {
86 | Cookies.remove('wallets');
87 | Cookies.set('wallets', wallets, { expires: 30 });
88 | }
89 | }
90 | });
91 |
92 |
93 |
94 | $.getJSON('/api/stats', function(data) {
95 | statData = data;
96 | });
97 |
--------------------------------------------------------------------------------
/libs/apiCoinWarz.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var nonce = require('nonce');
3 |
4 | module.exports = function() {
5 | 'use strict';
6 |
7 | // Module dependencies
8 |
9 | // Constants
10 | var version = '0.0.1',
11 | PUBLIC_API_URL = 'http://www.coinwarz.com/v1/api/profitability/?apikey=YOUR_API_KEY&algo=all',
12 | USER_AGENT = 'nomp/node-open-mining-portal'
13 |
14 | // Constructor
15 | function Cryptsy(key, secret){
16 | // Generate headers signed by this user's key and secret.
17 | // The secret is encapsulated and never exposed
18 | this._getPrivateHeaders = function(parameters){
19 | var paramString, signature;
20 |
21 | if (!key || !secret){
22 | throw 'CoinWarz: Error. API key and secret required';
23 | }
24 |
25 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar`
26 | paramString = Object.keys(parameters).sort().map(function(param){
27 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
28 | }).join('&');
29 |
30 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
31 |
32 | return {
33 | Key: key,
34 | Sign: signature
35 | };
36 | };
37 | }
38 |
39 | // If a site uses non-trusted SSL certificates, set this value to false
40 | Cryptsy.STRICT_SSL = true;
41 |
42 | // Helper methods
43 | function joinCurrencies(currencyA, currencyB){
44 | return currencyA + '_' + currencyB;
45 | }
46 |
47 | // Prototype
48 | CoinWarz.prototype = {
49 | constructor: CoinWarz,
50 |
51 | // Make an API request
52 | _request: function(options, callback){
53 | if (!('headers' in options)){
54 | options.headers = {};
55 | }
56 |
57 | options.headers['User-Agent'] = USER_AGENT;
58 | options.json = true;
59 | options.strictSSL = CoinWarz.STRICT_SSL;
60 |
61 | request(options, function(err, response, body) {
62 | callback(err, body);
63 | });
64 |
65 | return this;
66 | },
67 |
68 | // Make a public API request
69 | _public: function(parameters, callback){
70 | var options = {
71 | method: 'GET',
72 | url: PUBLIC_API_URL,
73 | qs: parameters
74 | };
75 |
76 | return this._request(options, callback);
77 | },
78 |
79 |
80 | /////
81 |
82 |
83 | // PUBLIC METHODS
84 |
85 | getTicker: function(callback){
86 | var parameters = {
87 | method: 'marketdatav2'
88 | };
89 |
90 | return this._public(parameters, callback);
91 | },
92 |
93 | getOrderBook: function(currencyA, currencyB, callback){
94 | var parameters = {
95 | command: 'returnOrderBook',
96 | currencyPair: joinCurrencies(currencyA, currencyB)
97 | };
98 |
99 | return this._public(parameters, callback);
100 | },
101 |
102 | getTradeHistory: function(currencyA, currencyB, callback){
103 | var parameters = {
104 | command: 'returnTradeHistory',
105 | currencyPair: joinCurrencies(currencyA, currencyB)
106 | };
107 |
108 | return this._public(parameters, callback);
109 | },
110 |
111 |
112 | ////
113 |
114 | return CoinWarz;
115 | }();
116 |
--------------------------------------------------------------------------------
/website/static/admin.js:
--------------------------------------------------------------------------------
1 | var docCookies = {
2 | getItem: function (sKey) {
3 | return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
4 | },
5 | setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
6 | if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
7 | var sExpires = "";
8 | if (vEnd) {
9 | switch (vEnd.constructor) {
10 | case Number:
11 | sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
12 | break;
13 | case String:
14 | sExpires = "; expires=" + vEnd;
15 | break;
16 | case Date:
17 | sExpires = "; expires=" + vEnd.toUTCString();
18 | break;
19 | }
20 | }
21 | document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
22 | return true;
23 | },
24 | removeItem: function (sKey, sPath, sDomain) {
25 | if (!sKey || !this.hasItem(sKey)) { return false; }
26 | document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
27 | return true;
28 | },
29 | hasItem: function (sKey) {
30 | return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
31 | }
32 | };
33 |
34 | var password = docCookies.getItem('password');
35 |
36 |
37 | function showLogin(){
38 | $('#adminCenter').hide();
39 | $('#passwordForm').show();
40 | }
41 |
42 | function showAdminCenter(){
43 | $('#passwordForm').hide();
44 | $('#adminCenter').show();
45 | }
46 |
47 | function tryLogin(){
48 | apiRequest('pools', {}, function(response){
49 | showAdminCenter();
50 | displayMenu(response.result)
51 | });
52 | }
53 |
54 | function displayMenu(pools){
55 | $('#poolList').after(Object.keys(pools).map(function(poolName){
56 | return '';
57 | }).join(''));
58 | }
59 |
60 | function apiRequest(func, data, callback){
61 | var httpRequest = new XMLHttpRequest();
62 | httpRequest.onreadystatechange = function(){
63 | if (httpRequest.readyState === 4 && httpRequest.responseText){
64 | if (httpRequest.status === 401){
65 | docCookies.removeItem('password');
66 | $('#password').val('');
67 | showLogin();
68 | alert('Incorrect Password');
69 | }
70 | else{
71 | var response = JSON.parse(httpRequest.responseText);
72 | callback(response);
73 | }
74 | }
75 | };
76 | httpRequest.open('POST', '/api/admin/' + func);
77 | data.password = password;
78 | httpRequest.setRequestHeader('Content-Type', 'application/json');
79 | httpRequest.send(JSON.stringify(data));
80 | }
81 |
82 | if (password){
83 | tryLogin();
84 | }
85 | else{
86 | showLogin();
87 | }
88 |
89 | $('#passwordForm').submit(function(event){
90 | event.preventDefault();
91 | password = $('#password').val();
92 | if (password){
93 | if ($('#remember').is(':checked'))
94 | docCookies.setItem('password', password, Infinity);
95 | else
96 | docCookies.setItem('password', password);
97 | tryLogin();
98 | }
99 | return false;
100 | });
101 |
--------------------------------------------------------------------------------
/website/pages/miner_stats.html:
--------------------------------------------------------------------------------
1 |
70 |
71 |
72 |
73 |
Account Details
74 |
75 |
76 |
77 | Total Immature:
78 | Total Balance:
79 | Total Paid:
81 |
82 |
96 |
97 |
98 |
Worker Hashrate HistoryColors are random! If you don't like the ones given, simply refresh the page!
99 |
100 |
101 |
102 |
103 |
104 |
105 |
108 |
109 |
110 |
121 |
--------------------------------------------------------------------------------
/website/static/fontawesome.js:
--------------------------------------------------------------------------------
1 | window.FontAwesomeKitConfig = {"asyncLoading":{"enabled":true},"autoA11y":{"enabled":true},"baseUrl":"https://kit-free.fontawesome.com","license":"free","method":"css","minify":{"enabled":true},"v4shim":{"enabled":true},"version":"latest"};
2 | !function(){!function(){if(!(void 0===window.Element||"classList"in document.documentElement)){var e,t,n,i=Array.prototype,o=i.push,a=i.splice,s=i.join;r.prototype={add:function(e){this.contains(e)||(o.call(this,e),this.el.className=this.toString())},contains:function(e){return-1!=this.el.className.indexOf(e)},item:function(e){return this[e]||null},remove:function(e){if(this.contains(e)){for(var t=0;t
x[1])));
8 | poolHashrateChart = createDefaultLineChart(
9 | document.getElementById("poolHashChart").getContext('2d'),
10 | [{
11 | label: 'Actual',
12 | fill: false,
13 | data: stats.hashrate.map(x => {
14 | return {
15 | t: x[0],
16 | y: getScaledHashrate(x[1], maxScale[2])
17 | }
18 | }),
19 | borderWidth: 2,
20 | backgroundColor: '#348EA9',
21 | borderColor: '#348EA9'
22 | },
23 | {
24 | label: 'Averaged',
25 | fill: false,
26 | data: stats.averagedHashrate.map(x => {
27 | return {
28 | t: x[0],
29 | y: getScaledHashrate(x[1], maxScale[2])
30 | }
31 | }),
32 | borderWidth: 2,
33 | backgroundColor: '#E81D62',
34 | borderColor: '#E81D62'
35 | }],
36 | 'Time',
37 | maxScale[1]
38 | );
39 | poolWorkerChart = createLineChart(
40 | document.getElementById("poolWorkerChart").getContext('2d'),
41 | [{
42 | label: 'Actual',
43 | fill: false,
44 | data: stats.workers.map(x => {
45 | return {
46 | t: x[0],
47 | y: x[1]
48 | }
49 | }),
50 | borderWidth: 2,
51 | backgroundColor: '#0061B5',
52 | borderColor: '#0061B5'
53 | },
54 | {
55 | label: 'Averaged',
56 | fill: false,
57 | data: stats.averagedWorkers.map(x => {
58 | return {
59 | t: x[0],
60 | y: x[1]
61 | }
62 | }),
63 | borderWidth: 2,
64 | backgroundColor: '#FF9400',
65 | borderColor: '#FF9400'
66 | }],
67 | 'Time',
68 | 'Workers',
69 | {
70 | beginAtZero: true,
71 | fixedStepSize: 1
72 | }
73 | );
74 | poolBlockChart = createLineChart(
75 | document.getElementById("blockChart").getContext('2d'),
76 | [{
77 | label: 'Currently Pending',
78 | fill: true,
79 | steppedLine: true,
80 | data: stats.blocks.map(x => {
81 | return {
82 | t: x[0],
83 | y: x[1]
84 | }
85 | }),
86 | borderWidth: 1,
87 | backgroundColor: '#FBA41F',
88 | borderColor: '#FBA41F'
89 | }],
90 | 'Time',
91 | 'Blocks',
92 | {
93 | beginAtZero: true,
94 | fixedStepSize: 1
95 | }
96 | );
97 | }
98 |
99 | $.getJSON('/api/pool_stats', function(data) {
100 | if (document.hidden) return;
101 | //Add pool to tracker
102 | addPoolToTracker(data, poolName, function() {
103 | displayCharts();
104 | });
105 | });
106 |
107 | statsSource.addEventListener('message', function(e) {
108 | var stats = JSON.parse(e.data);
109 | updatePoolData(stats, poolName, function(pool) {
110 | var max = Math.max.apply(null, pool.hashrate.map(x => x[1]));
111 | var pair = getReadableHashRatePair(max);
112 | var hash = getScaledHashrate(poolName in stats.pools ? stats.pools[poolName].hashrate : 0, pair[2]);
113 | $("#validShares").text(poolName in stats.pools ? stats.pools[poolName].poolStats.validShares : 0);
114 | $("#poolHashRate").text((!isNaN(hash) ? hash : 0) + ' ' + (pair[1] ? pair[1] : 'H/s'));
115 | $("#poolWorkers").text(poolName in stats.pools ? stats.pools[poolName].workerCount : 0);
116 | $("#pendingBlocks").text(poolName in stats.pools ? stats.pools[poolName].blocks.pending : 0);
117 | $("#confirmedBlocks").text(poolName in stats.pools ? stats.pools[poolName].blocks.confirmed : 0);
118 | var time = stats.time * 1000;
119 | var avg = pool.averagedHashrate;
120 | addChartData(poolHashrateChart, poolHashrateChart.data.datasets[0], {t: time, y: hash}, false);
121 | addChartData(poolHashrateChart, poolHashrateChart.data.datasets[1], {t: time, y: getScaledHashrate(avg[avg.length - 1][1], pair[2])}, true);
122 | addChartData(poolBlockChart, poolBlockChart.data.datasets[0], {t: time, y: poolName in stats.pools ? stats.pools[poolName].blocks.pending : 0}, true);
123 | });
124 | }, false);
125 |
--------------------------------------------------------------------------------
/website/static/methods.js:
--------------------------------------------------------------------------------
1 | function calculateExpMovingAvg(mArray, mRange) {
2 | var k = 2/ (mRange + 1);
3 | // first item is just the same as the first item in the input
4 | emaArray = [[mArray[0][0], mArray[0][1]]];
5 | // for the rest of the items, they are computed with the previous one
6 | for (var i = 1; i < mArray.length; i++) {
7 | var height = mArray[i][1] * k + emaArray[i - 1][1] * (1 - k);
8 | emaArray.push([mArray[i][0], height]);
9 | }
10 | return emaArray;
11 | }
12 |
13 | function capFirst(s) {
14 | return s.charAt(0).toUpperCase() + s.slice(1);
15 | }
16 |
17 | function getRandomInt(min, max) {
18 | return Math.floor(Math.random() * (max - min)) + min;
19 | }
20 |
21 | function generateName(){
22 | var name1 = ['raging', 'mad', 'hashing', 'cool', 'rich', 'honorable', 'king',
23 | 'fast', 'killer', 'sweet'];
24 |
25 | var name2 = ['cromulon', 'computer', 'hasher', 'PC', 'rig', 'miner', 'otter',
26 | 'cronenberg', 'gazorpazorp'];
27 |
28 | var name = name1[Math.floor(Math.random() * name1.length)].toLowerCase() + name2[Math.floor(Math.random() * name2.length)].toLowerCase();
29 | return name;
30 |
31 | }
32 |
33 | function getRandomColor() {
34 | var letters = '0123456789ABCDEF'.split('');
35 | var color = '#';
36 | for (var i = 0; i < 6; i++ ) {
37 | color += letters[Math.floor(Math.random() * 16)];
38 | }
39 | return color;
40 | }
41 |
42 | function getRandomPastelColor() {
43 | var r = (Math.round(Math.random() * 127) + 127).toString(16);
44 | var g = (Math.round(Math.random() * 127) + 127).toString(16);
45 | var b = (Math.round(Math.random() * 127) + 127).toString(16);
46 | return '#' + r + g + b;
47 | }
48 |
49 | function addChartData(chart, dataset, data, update) {
50 | dataset.data.shift();
51 | dataset.data.push(data);
52 | if(update){
53 | chart.update();
54 | }
55 | }
56 |
57 | this.getReadableHashRate = function(hashrate) {
58 | hashrate = (hashrate * 1000000);
59 | if(hashrate < 1000000){
60 | hashrate = hashrate * 100000;
61 | }
62 | var i = Math.max(0, Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1));
63 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1);
64 | return hashrate.toFixed(2);
65 | };
66 |
67 | this.getScaledHashrate = function(hashrate, i) {
68 | hashrate = (hashrate * 1000000);
69 | if(hashrate < 1000000){
70 | hashrate = hashrate * 100000;
71 | }
72 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1);
73 | return hashrate.toFixed(2);
74 | };
75 |
76 | this.getReadableHashRateString = function(hashrate) {
77 | hashrate = (hashrate * 1000000);
78 | if(hashrate < 1000000){
79 | hashrate = hashrate * 100000;
80 | }
81 | var byteUnits = [' H/s', ' KH/s', ' MH/s', ' GH/s', ' TH/s', ' PH/s'];
82 | var i = Math.max(0, Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1));
83 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1);
84 |
85 | return hashrate.toFixed(2) + ' ' + byteUnits[i];
86 | };
87 |
88 | this.getReadableHashRatePair = function(hashrate) {
89 | hashrate = (hashrate * 1000000);
90 | if(hashrate < 1000000){
91 | hashrate = hashrate * 100000;
92 | }
93 | var byteUnits = [' H/s', ' KH/s', ' MH/s', ' GH/s', ' TH/s', ' PH/s'];
94 | var i = Math.max(0, Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1));
95 | hashrate = (hashrate/1000) / Math.pow(1000, i + 1);
96 |
97 | return [hashrate.toFixed(2), byteUnits[i], i];
98 | };
99 |
100 | function createDefaultLineChart(ctx, datasets, xLabel, yLabel) {
101 | return createLineChart(ctx, datasets, xLabel, yLabel, { beginAtZero: true });
102 | }
103 |
104 | function createLineChart(ctx, datasets, xLabel, yLabel, ticks) {
105 | return new Chart(ctx, {
106 | type: 'line',
107 | data: {
108 | datasets: datasets
109 | },
110 | options: {
111 | spanGaps: true,
112 | animation: {
113 | easing: 'easeInExpo',
114 | duration: 1000,
115 | xAxis: true,
116 | yAxis: true,
117 | },
118 | responsive: true,
119 | maintainAspectRatio: false,
120 | elements: {
121 | point: { radius: 0 }
122 | },
123 | scales: {
124 | xAxes: [{
125 | type: 'time'
126 | }],
127 | yAxes: [{
128 | ticks: ticks,
129 | display: true,
130 | scaleLabel: {
131 | display: true,
132 | labelString: yLabel
133 | }
134 | }]
135 | }
136 | }
137 | });
138 | }
139 |
--------------------------------------------------------------------------------
/libs/shareProcessor.js:
--------------------------------------------------------------------------------
1 | var redis = require('redis');
2 | var Stratum = require('stratum-pool');
3 |
4 | const loggerFactory = require('./logger.js');
5 |
6 | const logger = loggerFactory.getLogger('ShareProcessor', 'system');
7 | /*
8 | This module deals with handling shares when in internal payment processing mode. It connects to a redis
9 | database and inserts shares with the database structure of:
10 |
11 | key: coin_name + ':' + block_height
12 | value: a hash with..
13 | key:
14 |
15 | */
16 |
17 |
18 |
19 | module.exports = function(poolConfig){
20 |
21 | var redisConfig = poolConfig.redis;
22 |
23 | var coin = poolConfig.coin.name;
24 | var forkId = process.env.forkId;
25 | let logger = loggerFactory.getLogger(`ShareProcessor [:${forkId}]`, coin);
26 |
27 |
28 | var logSystem = 'Pool';
29 | var logComponent = coin;
30 | var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
31 |
32 | var connection = redis.createClient(redisConfig.port, redisConfig.host);
33 |
34 | connection.on('ready', function(){
35 | logger.debug('Share processing setup with redis (' + redisConfig.host +
36 | ':' + redisConfig.port + ')');
37 | });
38 | connection.on('error', function(err){
39 | logger.error(logSystem, logComponent, logSubCat, 'Redis client had an error: ' + JSON.stringify(err))
40 | });
41 | connection.on('end', function(){
42 | logger.error(logSystem, logComponent, logSubCat, 'Connection to redis database has been ended');
43 | });
44 |
45 | connection.info(function(error, response){
46 | if (error){
47 | logger.error(logSystem, logComponent, logSubCat, 'Redis version check failed');
48 | return;
49 | }
50 | var parts = response.split('\r\n');
51 | var version;
52 | var versionString;
53 | for (var i = 0; i < parts.length; i++){
54 | if (parts[i].indexOf(':') !== -1){
55 | var valParts = parts[i].split(':');
56 | if (valParts[0] === 'redis_version'){
57 | versionString = valParts[1];
58 | version = parseFloat(versionString);
59 | break;
60 | }
61 | }
62 | }
63 | if (!version){
64 | logger.error(logSystem, logComponent, logSubCat, 'Could not detect redis version - but be super old or broken');
65 | }
66 | else if (version < 2.6){
67 | logger.error(logSystem, logComponent, logSubCat, "You're using redis version " + versionString + " the minimum required version is 2.6. Follow the damn usage instructions...");
68 | }
69 | });
70 |
71 |
72 | this.handleShare = function(isValidShare, isValidBlock, shareData){
73 |
74 | var redisCommands = [];
75 |
76 | if (isValidShare){
77 | redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]);
78 | redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]);
79 | }
80 | else{
81 | redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]);
82 | }
83 | /* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it
84 | doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to
85 | generate hashrate for each worker and pool. */
86 | var dateNow = Date.now();
87 | var hashrateData = [ isValidShare ? shareData.difficulty : -shareData.difficulty, shareData.worker, dateNow];
88 | redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]);
89 |
90 | if (isValidBlock){
91 |
92 | redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]);
93 |
94 | redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]);
95 |
96 | redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]);
97 |
98 | }
99 | else if (shareData.blockHash){
100 |
101 | redisCommands.push(['hincrby', coin + ':stats', 'invalidBlocks', 1]);
102 |
103 | }
104 |
105 | connection.multi(redisCommands).exec(function(err, replies){
106 | if (err)
107 | logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err));
108 | });
109 |
110 |
111 | };
112 |
113 | };
114 |
--------------------------------------------------------------------------------
/website/pages/pools.html:
--------------------------------------------------------------------------------
1 |
2 |
Quick Start Commands (GPU Algorithms):
3 |
4 | ccminer -a <ALGORITHM> -o stratum+tcp://{{=it.portalConfig.website.stratumHost}}:<PORT> -u <WALLET.WORKER> -p <ANYTHING>
5 |
6 |
7 | sgminer -k <ALGORITHM> -o stratum+tcp://{{=it.portalConfig.website.stratumHost}}:<PORT> -u <WALLET.WORKER> -p <ANYTHING>
8 |
9 |
10 | t-rex -a <ALGORITHM> -o stratum+tcp://{{=it.portalConfig.website.stratumHost}}:<PORT> -u <WALLET.WORKER> -p <ANYTHING>
11 |
12 |
Doesn't make sense? Head over to Getting Started for more help!
13 |
14 |
15 |
16 |
17 |
18 | Coin
19 | Symbol
20 | Algo
21 | Ports
22 | N. Hash Rate
23 | Hash Rate
24 | Mature
25 | Pending
26 | Workers
27 | Difficulty
28 | Change In
29 | Fee
30 | Donations:
31 |
32 |
33 |
34 |
35 | {{ var sortedPools = it.stats.pools; }}
36 |
37 |
38 |
39 | {{ for(var pool in it.stats.pools) { }}
40 |
41 |
42 |
43 | {{=it.stats.pools[pool].name.charAt(0).toUpperCase() + it.stats.pools[pool].name.slice(1)}}
44 |
45 | {{=it.stats.pools[pool].symbol}}
46 | {{=it.stats.pools[pool].algorithm}}
47 |
48 |
49 | {{ for(var pool2 in it.stats.pools) { }}
50 | {{ if(pool2 != pool) continue; }}
51 | {{=Object.keys(it.poolsConfigs[pool2].ports)}}
52 | {{ break; }}
53 | {{ } }}
54 |
55 |
56 | {{=it.stats.pools[pool].poolStats.networkSolsString}}
57 |
58 | {{=it.stats.pools[pool].hashrateString}}
59 |
60 | {{=it.stats.pools[pool].blocks.confirmed}}
61 |
62 | {{=it.stats.pools[pool].blocks.pending}}
63 |
64 | {{=it.stats.pools[pool].workerCount}}
65 |
66 | {{=Number(Math.round(it.stats.pools[pool].poolStats.networkDiff + 'e' + 4) + 'e-' + 4)}}
67 |
68 | {{ var blocktime = it.stats.pools[pool].blockTime || 0; }}
69 | {{ var blockchange = it.stats.pools[pool].blockChange || 0; }}
70 | {{ var blockheight = it.stats.pools[pool].poolStats.networkBlocks || 0; }}
71 |
72 | {{ var changehours = Number.parseFloat((((blockchange) - (blockheight % blockchange)) / blocktime)).toFixed(2) || 0; }}
73 | {{ var changeblocks = ((blockchange) - (blockheight % blockchange)) || 0; }}
74 |
75 | {{=changeblocks}} blocks (appx. {{=changehours}} hours)
76 |
77 | {{ var total = 0.0; }}
78 | {{ var rewardRecipients = it.stats.pools[pool].rewardRecipients || {}; }}
79 | {{ for (var r in rewardRecipients) { }}
80 | {{ total += rewardRecipients[r]; }}
81 | {{ } }}
82 |
83 | {{=total}}%
84 |
85 | {{=(it.poolsConfigs[pool].donateaddress||'none')}}
86 |
87 | {{ } }}
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/config_example.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "poolname": "My Cool Pool",
4 |
5 | "devmode": false,
6 | "devmodePayMinimim": 0.25,
7 | "devmodePayInterval": 120,
8 |
9 | "logips": true,
10 | "anonymizeips": true,
11 | "ipv4bits": 16,
12 | "ipv6bits": 16,
13 |
14 | "poolwarningmsg": "WARNING: POOL IS DEFAULT CONFIGURATION! PLEASE ENSURE TO CHANGE CONFIGURATION!",
15 |
16 | "defaultCoin": "pexacoin",
17 |
18 | "poollogo": "/local/file/path/or_a_web_address.to.png",
19 |
20 | "discordtwitterfacebook": "Join our #mining channel on Discord: https://discord.gg/vzcbVNW ",
21 |
22 | "pagetitle": "My Cool Pool - 0% Fees Promo - Run by professionals",
23 | "pageauthor": "My Name Is...",
24 | "pagedesc": "A reliable, low fee, easy to use mining pool for cryptocurrency! No matter your experience with mining cryptocurrency, we make it easy! Get started mining today!",
25 | "pagekeywds": "GPU,CPU,Hash,Hashrate,Cryptocurrency,Crypto,Mining,Pool,Bitcoin,Raven,Ravencoin,Wavi,Wavicoin,Dixicoin,Dixi,QBic,QBicCoin,Easy,Simple,How,To",
26 |
27 | "btcdonations": "18TmiWzbMLyf7MvQMcNWh3hUBVrxBgzrWi",
28 | "ltcdonations": "LX1fUwLVcAaRXvP67ZcqUvjjteaKx1nAvL",
29 | "ethdonations": "0x52fD0B6847E1D3cEc5600359f24d671FdE2Bc65B",
30 | "xmrdonations": "4AVabKt1zGhUckqWC5DKtkcc49ChYFuSETzvDZFfVnUYYY5vC4CSZ9xYzmCWvx7ePYZ9YvxgYbYsLL1L9TfQy2hi5Awsc7d",
31 |
32 | "logger" : {
33 | "level" : "debug",
34 | "file" : "/root/logs/nomp_debug.log"
35 | },
36 |
37 | "cliHost": "127.0.0.1",
38 | "cliPort": 17117,
39 |
40 | "clustering": {
41 | "enabled": true,
42 | "forks": "auto"
43 | },
44 |
45 | "defaultPoolConfigs": {
46 | "blockRefreshInterval": 1000,
47 | "jobRebroadcastTimeout": 55,
48 | "connectionTimeout": 600,
49 | "emitInvalidBlockHashes": false,
50 | "validateWorkerUsername": true,
51 | "tcpProxyProtocol": false,
52 | "banning": {
53 | "enabled": true,
54 | "time": 600,
55 | "invalidPercent": 50,
56 | "checkThreshold": 500,
57 | "purgeInterval": 300
58 | },
59 | "redis": {
60 | "host": "127.0.0.1",
61 | "port": 6379
62 | }
63 | },
64 |
65 | "website": {
66 | "enabled": true,
67 | "sslenabled": true,
68 | "sslforced": true,
69 | "host": "0.0.0.0",
70 | "port": 80,
71 | "sslport": 443,
72 | "sslkey": "/root/EASYNOMP/certs/privkey.pem",
73 | "sslcert": "/root/EASYNOMP/certs/fullchain.pem",
74 | "stratumHost": "MY_SERVER_IP_OR_DOMAIN_NAME",
75 | "stats": {
76 | "updateInterval": 5,
77 | "historicalRetention": 43200,
78 | "hashrateWindow": 600
79 | },
80 | "adminCenter": {
81 | "enabled": false,
82 | "password": "NOT_WORKING_YET_:P_LESHACAT_CAN_DO_ADMIN_PANEL_FUNCTIONALITY_TOO"
83 | }
84 | },
85 |
86 | "redis": {
87 | "host": "127.0.0.1",
88 | "port": 6379
89 | },
90 |
91 | "switching": {
92 | "switch1": {
93 | "enabled": false,
94 | "algorithm": "sha256",
95 | "ports": {
96 | "3333": {
97 | "diff": 10,
98 | "varDiff": {
99 | "minDiff": 16,
100 | "maxDiff": 512,
101 | "targetTime": 15,
102 | "retargetTime": 90,
103 | "variancePercent": 30
104 | }
105 | }
106 | }
107 | },
108 | "switch2": {
109 | "enabled": false,
110 | "algorithm": "scrypt",
111 | "ports": {
112 | "4444": {
113 | "diff": 10,
114 | "varDiff": {
115 | "minDiff": 16,
116 | "maxDiff": 512,
117 | "targetTime": 15,
118 | "retargetTime": 90,
119 | "variancePercent": 30
120 | }
121 | }
122 | }
123 | },
124 | "switch3": {
125 | "enabled": false,
126 | "algorithm": "x11",
127 | "ports": {
128 | "5555": {
129 | "diff": 0.001,
130 | "varDiff": {
131 | "minDiff": 0.001,
132 | "maxDiff": 1,
133 | "targetTime": 15,
134 | "retargetTime": 60,
135 | "variancePercent": 30
136 | }
137 | }
138 | }
139 | }
140 | },
141 |
142 | "profitSwitch": {
143 | "enabled": false,
144 | "updateInterval": 600,
145 | "depth": 0.90,
146 | "usePoloniex": true,
147 | "useCryptsy": true,
148 | "useMintpal": true,
149 | "useBittrex": true
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/libs/mposCompatibility.js:
--------------------------------------------------------------------------------
1 | var mysql = require('mysql');
2 | var cluster = require('cluster');
3 |
4 | const logger = require('./logger.js').getLogger('MPOSCompatibility', 'system');
5 | module.exports = function (poolConfig) {
6 |
7 | var mposConfig = poolConfig.mposMode;
8 | var coin = poolConfig.coin.name;
9 |
10 | var connection = mysql.createPool({
11 | host: mposConfig.host,
12 | port: mposConfig.port,
13 | user: mposConfig.user,
14 | password: mposConfig.password,
15 | database: mposConfig.database
16 | });
17 |
18 |
19 | var logIdentify = 'MySQL';
20 | var logComponent = coin;
21 |
22 |
23 | this.handleAuth = function (workerName, password, authCallback) {
24 |
25 | if (poolConfig.validateWorkerUsername !== true && mposConfig.autoCreateWorker !== true) {
26 | authCallback(true);
27 | return;
28 | }
29 |
30 | connection.query(
31 | 'SELECT password FROM pool_worker WHERE username = LOWER(?)',
32 | [workerName.toLowerCase()],
33 | function (err, result) {
34 | if (err) {
35 | logger.error('Database error when authenticating worker, err = %s', JSON.stringify(err));
36 | authCallback(false);
37 | }
38 | else if (!result[0]) {
39 | if (mposConfig.autoCreateWorker) {
40 | var account = workerName.split('.')[0];
41 | connection.query(
42 | 'SELECT id,username FROM accounts WHERE username = LOWER(?)',
43 | [account.toLowerCase()],
44 | function (err, result) {
45 | if (err) {
46 | logger.error('Database error when authenticating account, err = %s', JSON.stringify(err));
47 | authCallback(false);
48 | } else if (!result[0]) {
49 | authCallback(false);
50 | } else {
51 | connection.query(
52 | "INSERT INTO `pool_worker` (`account_id`, `username`, `password`) VALUES (?, ?, ?);",
53 | [result[0].id, workerName.toLowerCase(), password],
54 | function (err, result) {
55 | if (err) {
56 | logger.error('Database error when insert worker, err = %s', JSON.stringify(err));
57 | authCallback(false);
58 | } else {
59 | authCallback(true);
60 | }
61 | })
62 | }
63 | }
64 | );
65 | }
66 | else {
67 | authCallback(false);
68 | }
69 | }
70 | else if (mposConfig.checkPassword && result[0].password !== password) {
71 | authCallback(false);
72 | }
73 | else {
74 | authCallback(true);
75 | }
76 | }
77 | );
78 |
79 | };
80 |
81 | this.handleShare = function (isValidShare, isValidBlock, shareData) {
82 |
83 | var dbData = [
84 | shareData.ip,
85 | shareData.worker,
86 | isValidShare ? 'Y' : 'N',
87 | isValidBlock ? 'Y' : 'N',
88 | shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1),
89 | typeof(shareData.error) === 'undefined' ? null : shareData.error,
90 | shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '')
91 | ];
92 | connection.query(
93 | 'INSERT INTO `shares` SET time = NOW(), rem_host = ?, username = ?, our_result = ?, upstream_result = ?, difficulty = ?, reason = ?, solution = ?',
94 | dbData,
95 | function (err, result) {
96 | if (err) {
97 | logger.error('Insert error when adding share, err = %s', JSON.stringify(err));
98 | }
99 | else {
100 | logger.debug('Share inserted');
101 | }
102 | }
103 | );
104 | };
105 |
106 | this.handleDifficultyUpdate = function (workerName, diff) {
107 |
108 | connection.query(
109 | 'UPDATE `pool_worker` SET `difficulty` = ' + diff + ' WHERE `username` = ' + connection.escape(workerName),
110 | function (err, result) {
111 | if (err) {
112 | logger.error('Error when updating worker diff, err = %s', JSON.stringify(err));
113 | }
114 | else if (result.affectedRows === 0) {
115 | connection.query('INSERT INTO `pool_worker` SET ?', {username: workerName, difficulty: diff});
116 | }
117 | else {
118 | logger.debug('Updated difficulty successfully, result = %s', result);
119 | }
120 | }
121 | );
122 | };
123 |
124 |
125 | };
126 |
--------------------------------------------------------------------------------
/website/pages/history.html:
--------------------------------------------------------------------------------
1 |
39 |
40 | {{ var sortedPools = Object.keys(it.stats.pools); }}
41 | {{ sortedPools.sort(function(a, b) { return a.workerCount - b.workerCount; }); }}
42 |
43 |
44 |
45 |
46 | Block History For
47 |
48 |
49 |
50 | {{=sortedPools[0].charAt(0).toUpperCase() + sortedPools[0].slice(1)}}
51 |
52 |
57 |
58 |
59 |
60 |
61 |
62 | {{=sortedPools[0].charAt(0).toUpperCase() + sortedPools[0].slice(1)}} History
63 |
64 |
65 |
66 |
67 | Pending Blocks:
68 | {{=it.stats.pools[sortedPools[0]].blocks.pending}}
69 |
70 |
71 | Confirmed Blocks:
72 | {{=it.stats.pools[sortedPools[0]].blocks.confirmed}}
73 |
74 |
75 |
76 |
77 |
Last 50 Confirmed Blocks
78 |
79 |
95 |
96 |
97 |
98 |
147 |
--------------------------------------------------------------------------------
/website/static/stats.js:
--------------------------------------------------------------------------------
1 | var poolWorkerData;
2 | var poolHashrateData;
3 | var poolBlockData;
4 |
5 | var poolWorkerChart;
6 | var poolHashrateChart;
7 | var poolBlockChart;
8 |
9 | var statData;
10 | var poolKeys;
11 |
12 | function buildChartData() {
13 |
14 | var pools = {};
15 |
16 | poolKeys = [];
17 | for (var i = 0; i < statData.length; i++) {
18 | for (var pool in statData[i].pools) {
19 | if (poolKeys.indexOf(pool) === -1)
20 | poolKeys.push(pool);
21 | }
22 | }
23 |
24 |
25 | for (var i = 0; i < statData.length; i++) {
26 | var time = statData[i].time * 1000;
27 | for (var f = 0; f < poolKeys.length; f++) {
28 | var pName = poolKeys[f];
29 | var a = pools[pName] = (pools[pName] || {
30 | hashrate: [],
31 | workers: [],
32 | blocks: []
33 | });
34 | if (pName in statData[i].pools) {
35 | a.hashrate.push([time, statData[i].pools[pName].hashrate]);
36 | a.workers.push([time, statData[i].pools[pName].workerCount]);
37 | a.blocks.push({ t: new Date(time), y: statData[i].pools[pName].blocks.pending})
38 | } else {
39 | a.hashrate.push([time, 0]);
40 | a.workers.push([time, 0]);
41 | a.blocks.push({x: time, y: 0})
42 | }
43 | }
44 | }
45 |
46 | poolWorkerData = [];
47 | poolHashrateData = [];
48 | poolBlockData = [];
49 |
50 | for (var pool in pools) {
51 | poolWorkerData.push({
52 | label: pool,
53 | value: pools[pool].workers[pools[pool].workers.length - 1][1]
54 | });
55 | poolHashrateData.push({
56 | label: pool,
57 | value: parseInt(pools[pool].hashrate[pools[pool].hashrate.length - 1][1] / 2048)
58 | });
59 | poolBlockData.push({
60 | label: pool,
61 | value: pools[pool].blocks
62 | });
63 | }
64 | }
65 |
66 | function getReadableHashRateString(hashrate) {
67 | var i = -1;
68 | var byteUnits = [' KH', ' MH', ' GH', ' TH', ' PH'];
69 | do {
70 | hashrate = hashrate / 1024;
71 | i++;
72 | } while (hashrate > 1024);
73 | return Math.round(hashrate) + byteUnits[i];
74 | }
75 |
76 | function timeOfDayFormat(timestamp) {
77 | var dStr = d3.time.format('%I:%M %p')(new Date(timestamp));
78 | if (dStr.indexOf('0') === 0) dStr = dStr.slice(1);
79 | return dStr;
80 | }
81 |
82 | function displayCharts() {
83 | var chartColors = [
84 | '#1976D2',
85 | '#388E3C',
86 | '#FBC02D',
87 | '#512DA8',
88 | '#C2185B'
89 | ];
90 | poolWorkerChart = new Chart($("#workerChart"), {
91 | type: 'pie',
92 | data: {
93 | labels: poolWorkerData.slice(0, 5).map(x => x.label),
94 | datasets: [{
95 | data: poolWorkerData.slice(0, 5).map(x => x.value),
96 | backgroundColor: chartColors
97 | }],
98 | },
99 | options: {
100 | responsive: true
101 | }
102 | });
103 |
104 | poolHashrateChart = new Chart($("#hashChart"), {
105 | type: 'pie',
106 | data: {
107 | labels: poolHashrateData.slice(0, 5).map(x => x.label),
108 | datasets: [{
109 | data: poolHashrateData.slice(0, 5).map(x => x.value),
110 | backgroundColor: chartColors
111 | }]
112 | },
113 | options: {
114 | responsive: true
115 | }
116 | });
117 |
118 | var blockData = [];
119 | var labels = poolBlockData.slice(0, 5).map(x => x.label);
120 | var values = poolBlockData.slice(0, 5).map(x => x.value);
121 | for(var i = 0; i < poolBlockData.length; i++) {
122 | blockData.push(
123 | {
124 | label: labels[i],
125 | data: values[i],
126 | backgroundColor: chartColors[i],
127 | borderColor: chartColors[i]
128 | }
129 | );
130 | }
131 | $("#blockChart").height = 200;
132 | poolBlockChart = new Chart($("#blockChart"), {
133 | type: 'line',
134 | data: {
135 | datasets: blockData
136 | },
137 | options: {
138 | maintainAspectRatio: false,
139 | responsive: true,
140 | scales: {
141 | xAxes: [{
142 | time: {
143 | unit: 'minute'
144 | }
145 | }]
146 | }
147 | }
148 | });
149 |
150 | }
151 |
152 | function pastelColors() {
153 | var r = (Math.round(Math.random() * 127) + 127).toString(16);
154 | var g = (Math.round(Math.random() * 127) + 127).toString(16);
155 | var b = (Math.round(Math.random() * 127) + 127).toString(16);
156 | return '#' + r + g + b;
157 | }
158 |
159 | function TriggerChartUpdates() {
160 | poolWorkerChart.update();
161 | poolHashrateChart.update();
162 | poolBlockChart.update();
163 | }
164 |
165 | $.getJSON('/api/pool_stats', function(data) {
166 | statData = data;
167 | buildChartData();
168 | displayCharts();
169 | });
170 |
171 |
172 |
173 | statsSource.addEventListener('message', function(e) {
174 | var stats = JSON.parse(e.data);
175 | statData.push(stats);
176 |
177 | var newPoolAdded = (function() {
178 | for (var p in stats.pools) {
179 | if (poolKeys.indexOf(p) === -1)
180 | return true;
181 | }
182 | return false;
183 | })();
184 |
185 | if (newPoolAdded || Object.keys(stats.pools).length > poolKeys.length) {
186 | buildChartData();
187 | displayCharts();
188 | } else {
189 | var time = stats.time * 1000;
190 | for (var f = 0; f < poolKeys.length; f++) {
191 | var pool = poolKeys[f];
192 | for (var i = 0; i < poolWorkerData.length; i++) {
193 | if (poolWorkerData[i].key === pool) {
194 | poolWorkerData[i].values.shift();
195 | poolWorkerData[i].values.push([time, pool in stats.pools ? stats.pools[pool].workerCount : 0]);
196 | break;
197 | }
198 | }
199 | for (var i = 0; i < poolHashrateData.length; i++) {
200 | if (poolHashrateData[i].key === pool) {
201 | poolHashrateData[i].values.shift();
202 | poolHashrateData[i].values.push([time, pool in stats.pools ? stats.pools[pool].hashrate : 0]);
203 | break;
204 | }
205 | }
206 | for (var i = 0; i < poolBlockData.length; i++) {
207 | if (poolBlockData[i].key === pool) {
208 | poolBlockData[i].values.shift();
209 | poolBlockData[i].values.push([time, pool in stats.pools ? stats.pools[pool].blocks.pending : 0]);
210 | break;
211 | }
212 | }
213 | }
214 | TriggerChartUpdates();
215 | }
216 |
217 | });
218 |
--------------------------------------------------------------------------------
/website/pages/pool_stats.html:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | General Information
21 | Pool Information
22 | Network Information
23 |
24 |
25 |
26 | {{ for(var pool in it.stats.pools) { }}
27 | {{ if(pool !== it.stats.coin) continue; }}
28 |
29 | Coin:
30 | {{=(String(it.stats.coin).charAt(0).toUpperCase() + String(it.stats.coin).slice(1))}}
31 | Workers:
32 | {{=it.stats.pools[pool].workerCount}}
33 | Hashrate:
34 | {{=it.stats.pools[pool].hashrateString}}
35 | Pending Blocks:
36 | {{=it.stats.pools[pool].blocks.pending}}
37 |
38 | Hashrate:
39 | {{=it.stats.pools[pool].poolStats.networkSolsString}}
40 |
41 | Height:
42 | {{=it.stats.pools[pool].poolStats.networkBlocks}}
43 |
44 |
45 |
46 | Ports:
47 | {{ for(var pool in it.poolsConfigs) { }}
48 | {{ if(pool !== it.stats.coin) continue; }}
49 | {{=Object.keys(it.poolsConfigs[pool].ports)}}
50 | {{ break; }}
51 | {{ } }}
52 | Valid Shares:
53 | {{=it.stats.pools[pool].poolStats.validShares}}
54 | Mature Blocks:
55 | {{=it.stats.pools[pool].blocks.confirmed}}
56 | Difficulty:
57 | {{=Number(Math.round(it.stats.pools[pool].poolStats.networkDiff + 'e' + 4) + 'e-' + 4)}}
58 |
59 |
61 |
62 | {{ var blocktime = it.stats.pools[pool].blockTime || 0; }}
63 | {{ var blockchange = it.stats.pools[pool].blockChange || 0; }}
64 | {{ var blockheight = it.stats.pools[pool].poolStats.networkBlocks || 0; }}
65 |
66 | {{ var changehours = Number.parseFloat((((blockchange) - (blockheight % blockchange)) / blocktime)).toFixed(2) || 0; }}
67 | {{ var changeblocks = ((blockchange) - (blockheight % blockchange)) || 0; }}
68 |
69 | Diff change in: {{=changeblocks}} blocks (appx. {{=changehours}} hours)
70 |
71 |
72 |
73 |
74 |
75 | {{=it.stats.pools[pool].name.charAt(0).toUpperCase() + it.stats.pools[pool].name.slice(1)}} Donations: {{=(it.poolsConfigs[pool].donateaddress||'none')}}
76 |
77 |
78 | {{ break; }}
79 | {{ } }}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
Hashrate History
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
Pending Block History
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
Worker History
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
126 |
--------------------------------------------------------------------------------
/libs/apiCryptsy.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var nonce = require('nonce');
3 |
4 | module.exports = function() {
5 | 'use strict';
6 |
7 | // Module dependencies
8 |
9 | // Constants
10 | var version = '0.1.0',
11 | PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php',
12 | PRIVATE_API_URL = 'https://api.cryptsy.com/api',
13 | USER_AGENT = 'nomp/node-open-mining-portal'
14 |
15 | // Constructor
16 | function Cryptsy(key, secret){
17 | // Generate headers signed by this user's key and secret.
18 | // The secret is encapsulated and never exposed
19 | this._getPrivateHeaders = function(parameters){
20 | var paramString, signature;
21 |
22 | if (!key || !secret){
23 | throw 'Cryptsy: Error. API key and secret required';
24 | }
25 |
26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar`
27 | paramString = Object.keys(parameters).sort().map(function(param){
28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
29 | }).join('&');
30 |
31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
32 |
33 | return {
34 | Key: key,
35 | Sign: signature
36 | };
37 | };
38 | }
39 |
40 | // If a site uses non-trusted SSL certificates, set this value to false
41 | Cryptsy.STRICT_SSL = true;
42 |
43 | // Helper methods
44 | function joinCurrencies(currencyA, currencyB){
45 | return currencyA + '_' + currencyB;
46 | }
47 |
48 | // Prototype
49 | Cryptsy.prototype = {
50 | constructor: Cryptsy,
51 |
52 | // Make an API request
53 | _request: function(options, callback){
54 | if (!('headers' in options)){
55 | options.headers = {};
56 | }
57 |
58 | options.headers['User-Agent'] = USER_AGENT;
59 | options.json = true;
60 | options.strictSSL = Cryptsy.STRICT_SSL;
61 |
62 | request(options, function(err, response, body) {
63 | callback(err, body);
64 | });
65 |
66 | return this;
67 | },
68 |
69 | // Make a public API request
70 | _public: function(parameters, callback){
71 | var options = {
72 | method: 'GET',
73 | url: PUBLIC_API_URL,
74 | qs: parameters
75 | };
76 |
77 | return this._request(options, callback);
78 | },
79 |
80 | // Make a private API request
81 | _private: function(parameters, callback){
82 | var options;
83 |
84 | parameters.nonce = nonce();
85 | options = {
86 | method: 'POST',
87 | url: PRIVATE_API_URL,
88 | form: parameters,
89 | headers: this._getPrivateHeaders(parameters)
90 | };
91 |
92 | return this._request(options, callback);
93 | },
94 |
95 |
96 | /////
97 |
98 |
99 | // PUBLIC METHODS
100 |
101 | getTicker: function(callback){
102 | var parameters = {
103 | method: 'marketdatav2'
104 | };
105 |
106 | return this._public(parameters, callback);
107 | },
108 |
109 | getOrderBook: function(currencyA, currencyB, callback){
110 | var parameters = {
111 | command: 'returnOrderBook',
112 | currencyPair: joinCurrencies(currencyA, currencyB)
113 | };
114 |
115 | return this._public(parameters, callback);
116 | },
117 |
118 | getTradeHistory: function(currencyA, currencyB, callback){
119 | var parameters = {
120 | command: 'returnTradeHistory',
121 | currencyPair: joinCurrencies(currencyA, currencyB)
122 | };
123 |
124 | return this._public(parameters, callback);
125 | },
126 |
127 |
128 | /////
129 |
130 |
131 | // PRIVATE METHODS
132 |
133 | myBalances: function(callback){
134 | var parameters = {
135 | command: 'returnBalances'
136 | };
137 |
138 | return this._private(parameters, callback);
139 | },
140 |
141 | myOpenOrders: function(currencyA, currencyB, callback){
142 | var parameters = {
143 | command: 'returnOpenOrders',
144 | currencyPair: joinCurrencies(currencyA, currencyB)
145 | };
146 |
147 | return this._private(parameters, callback);
148 | },
149 |
150 | myTradeHistory: function(currencyA, currencyB, callback){
151 | var parameters = {
152 | command: 'returnTradeHistory',
153 | currencyPair: joinCurrencies(currencyA, currencyB)
154 | };
155 |
156 | return this._private(parameters, callback);
157 | },
158 |
159 | buy: function(currencyA, currencyB, rate, amount, callback){
160 | var parameters = {
161 | command: 'buy',
162 | currencyPair: joinCurrencies(currencyA, currencyB),
163 | rate: rate,
164 | amount: amount
165 | };
166 |
167 | return this._private(parameters, callback);
168 | },
169 |
170 | sell: function(currencyA, currencyB, rate, amount, callback){
171 | var parameters = {
172 | command: 'sell',
173 | currencyPair: joinCurrencies(currencyA, currencyB),
174 | rate: rate,
175 | amount: amount
176 | };
177 |
178 | return this._private(parameters, callback);
179 | },
180 |
181 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){
182 | var parameters = {
183 | command: 'cancelOrder',
184 | currencyPair: joinCurrencies(currencyA, currencyB),
185 | orderNumber: orderNumber
186 | };
187 |
188 | return this._private(parameters, callback);
189 | },
190 |
191 | withdraw: function(currency, amount, address, callback){
192 | var parameters = {
193 | command: 'withdraw',
194 | currency: currency,
195 | amount: amount,
196 | address: address
197 | };
198 |
199 | return this._private(parameters, callback);
200 | }
201 | };
202 |
203 | return Cryptsy;
204 | }();
205 |
--------------------------------------------------------------------------------
/libs/apiPoloniex.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var nonce = require('nonce');
3 |
4 | module.exports = function() {
5 | 'use strict';
6 |
7 | // Module dependencies
8 |
9 | // Constants
10 | var version = '0.1.0',
11 | PUBLIC_API_URL = 'https://poloniex.com/public',
12 | PRIVATE_API_URL = 'https://poloniex.com/tradingApi',
13 | USER_AGENT = 'npm-crypto-apis/' + version
14 |
15 | // Constructor
16 | function Poloniex(key, secret){
17 | // Generate headers signed by this user's key and secret.
18 | // The secret is encapsulated and never exposed
19 | this._getPrivateHeaders = function(parameters){
20 | var paramString, signature;
21 |
22 | if (!key || !secret){
23 | throw 'Poloniex: Error. API key and secret required';
24 | }
25 |
26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar`
27 | paramString = Object.keys(parameters).sort().map(function(param){
28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
29 | }).join('&');
30 |
31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
32 |
33 | return {
34 | Key: key,
35 | Sign: signature
36 | };
37 | };
38 | }
39 |
40 | // If a site uses non-trusted SSL certificates, set this value to false
41 | Poloniex.STRICT_SSL = true;
42 |
43 | // Helper methods
44 | function joinCurrencies(currencyA, currencyB){
45 | return currencyA + '_' + currencyB;
46 | }
47 |
48 | // Prototype
49 | Poloniex.prototype = {
50 | constructor: Poloniex,
51 |
52 | // Make an API request
53 | _request: function(options, callback){
54 | if (!('headers' in options)){
55 | options.headers = {};
56 | }
57 |
58 | options.headers['User-Agent'] = USER_AGENT;
59 | options.json = true;
60 | options.strictSSL = Poloniex.STRICT_SSL;
61 |
62 | request(options, function(err, response, body) {
63 | callback(err, body);
64 | });
65 |
66 | return this;
67 | },
68 |
69 | // Make a public API request
70 | _public: function(parameters, callback){
71 | var options = {
72 | method: 'GET',
73 | url: PUBLIC_API_URL,
74 | qs: parameters
75 | };
76 |
77 | return this._request(options, callback);
78 | },
79 |
80 | // Make a private API request
81 | _private: function(parameters, callback){
82 | var options;
83 |
84 | parameters.nonce = nonce();
85 | options = {
86 | method: 'POST',
87 | url: PRIVATE_API_URL,
88 | form: parameters,
89 | headers: this._getPrivateHeaders(parameters)
90 | };
91 |
92 | return this._request(options, callback);
93 | },
94 |
95 |
96 | /////
97 |
98 |
99 | // PUBLIC METHODS
100 |
101 | getTicker: function(callback){
102 | var parameters = {
103 | command: 'returnTicker'
104 | };
105 |
106 | return this._public(parameters, callback);
107 | },
108 |
109 | get24hVolume: function(callback){
110 | var parameters = {
111 | command: 'return24hVolume'
112 | };
113 |
114 | return this._public(parameters, callback);
115 | },
116 |
117 | getOrderBook: function(currencyA, currencyB, callback){
118 | var parameters = {
119 | command: 'returnOrderBook',
120 | currencyPair: joinCurrencies(currencyA, currencyB)
121 | };
122 |
123 | return this._public(parameters, callback);
124 | },
125 |
126 | getTradeHistory: function(currencyA, currencyB, callback){
127 | var parameters = {
128 | command: 'returnTradeHistory',
129 | currencyPair: joinCurrencies(currencyA, currencyB)
130 | };
131 |
132 | return this._public(parameters, callback);
133 | },
134 |
135 |
136 | /////
137 |
138 |
139 | // PRIVATE METHODS
140 |
141 | myBalances: function(callback){
142 | var parameters = {
143 | command: 'returnBalances'
144 | };
145 |
146 | return this._private(parameters, callback);
147 | },
148 |
149 | myOpenOrders: function(currencyA, currencyB, callback){
150 | var parameters = {
151 | command: 'returnOpenOrders',
152 | currencyPair: joinCurrencies(currencyA, currencyB)
153 | };
154 |
155 | return this._private(parameters, callback);
156 | },
157 |
158 | myTradeHistory: function(currencyA, currencyB, callback){
159 | var parameters = {
160 | command: 'returnTradeHistory',
161 | currencyPair: joinCurrencies(currencyA, currencyB)
162 | };
163 |
164 | return this._private(parameters, callback);
165 | },
166 |
167 | buy: function(currencyA, currencyB, rate, amount, callback){
168 | var parameters = {
169 | command: 'buy',
170 | currencyPair: joinCurrencies(currencyA, currencyB),
171 | rate: rate,
172 | amount: amount
173 | };
174 |
175 | return this._private(parameters, callback);
176 | },
177 |
178 | sell: function(currencyA, currencyB, rate, amount, callback){
179 | var parameters = {
180 | command: 'sell',
181 | currencyPair: joinCurrencies(currencyA, currencyB),
182 | rate: rate,
183 | amount: amount
184 | };
185 |
186 | return this._private(parameters, callback);
187 | },
188 |
189 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){
190 | var parameters = {
191 | command: 'cancelOrder',
192 | currencyPair: joinCurrencies(currencyA, currencyB),
193 | orderNumber: orderNumber
194 | };
195 |
196 | return this._private(parameters, callback);
197 | },
198 |
199 | withdraw: function(currency, amount, address, callback){
200 | var parameters = {
201 | command: 'withdraw',
202 | currency: currency,
203 | amount: amount,
204 | address: address
205 | };
206 |
207 | return this._private(parameters, callback);
208 | }
209 | };
210 |
211 | return Poloniex;
212 | }();
213 |
--------------------------------------------------------------------------------
/website/pages/learn_more.html:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 |
32 |
Cryptocurrency Mining
33 |
You may have heard about Cryptocurrency or Bitcoin mining on the news, through a friend, or even just on a random search. No matter how you heard about it, you may be asking yourself what exactly is it? Is it something that I could start
34 | doing at home? Is it even worth my time? This page will give you a better understanding of what cryptocurrency mining is, and even show you how to get started!
35 |
36 |
37 |
38 |
39 |
So, What Is It?
40 |
41 | "Cryptocurrency mining is the process by which transactions are verified and added to the public ledger, known as the block chain, and the means through which new coins are released. Anyone with access to the internet
42 | and suitable hardware can participate in mining. The mining process involves compiling recent transactions into blocks and trying to solve a computationally difficult puzzle. The participant who first solves the
43 | puzzle gets to place the next block on the block chain and claim the rewards. The rewards, which incentivize mining, are both the transaction fees associated with the transactions compiled in the block as well as
44 | newly released coins."
45 |
46 |
47 |
48 |
49 |
50 |
51 |
Can I Mine with My Computer?
52 |
Anyone can mine; the only limiting factor being the hardware which you wish to mine from. If you have a GPU (Graphics Processing Unit) or a compatable CPU (Central Processing Unit), and mining software which supports your GPU/CPU as well as the algorithm of the coin you are trying to mine then
53 | you should have no issues! Don't know what mining software or algorithms are? Don't worry, we will cover that shortly!
54 |
55 |
56 |
57 |
58 |
What Is Mining Software?
59 |
Mining software, are applications, usually command line, that allows users to connect to the block chain or pool, receive work, and use hardware to solve work given. There are two very popular choices when it comes to software, CCMiner, Suprminer, TRex
60 | for Nvidia GPU's , Wildrig, SGMiner for AMD GPU's , CPUMINER-MULTI, CPUMINER-OPT for CPU's . A quick search online will give you more information about these programs, though look carefully, there could be malicious content; for safety we recommend find the source code for the
61 | project before downloading.
62 |
63 |
64 |
65 |
66 |
What Is an Algorithm?
67 |
An algorithm is defined as "a process or set of rules to be followed in calculations or other problem-solving operations, especially by a computer" . Cryptocurrencies miners use cryptographic algorithms such as SHA-2, Scrypt,
68 | and Equihash to generate hashes for the block chain. Each algorithm has positive and negatives for each given hardware setup, so researching your hardware prior to chosing a coin to mine, can make your venture much more profitable.
69 |
70 |
71 |
72 |
73 |
How Do I Start?
74 |
75 | Well firstly, you need to pick a mining software which is compatable with your system. While I will not be providing links to any mining software, if you refer back to popular choices I mentioned above, a quick online search will
76 | show you the software needed.
77 | Pick a coin you wish to mine. Remember each coin has an algorithm which needs to be accounted for, as you will need to verify that your mining software supports it. There is a README file usually included with most mining software,
78 | and inside you can see each supported algorithm, as well as other useful options.
79 | After you decided which coin, you need to get a wallet. Each coin provides it's own wallet software which you can download from the coin's website or project repository. Download the wallet, open it, let it sync (this could take
80 | a while), once it's syncs, find your wallet address and hold it for the next step.
81 | Inside of the folder of your mining software application, create a .bat or .cmd file. Edit the file and use our configuration generator on the Getting Started page to generate a personalized configuration, copy and paste the results
82 | into your file. Save it, and you should now be able to double click the file to start the miner. Good luck mining!
83 |
84 |
85 |
86 |
87 |
88 |
Having Trouble?
89 |
{{=it.portalConfig.discordtwitterfacebook}}
90 |
91 |
92 |
93 |
96 |
--------------------------------------------------------------------------------
/libs/apiMintpal.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var nonce = require('nonce');
3 |
4 | module.exports = function() {
5 | 'use strict';
6 |
7 | // Module dependencies
8 |
9 | // Constants
10 | var version = '0.1.0',
11 | PUBLIC_API_URL = 'https://api.mintpal.com/v2/market',
12 | PRIVATE_API_URL = 'https://api.mintpal.com/v2/market',
13 | USER_AGENT = 'nomp/node-open-mining-portal'
14 |
15 | // Constructor
16 | function Mintpal(key, secret){
17 | // Generate headers signed by this user's key and secret.
18 | // The secret is encapsulated and never exposed
19 | this._getPrivateHeaders = function(parameters){
20 | var paramString, signature;
21 |
22 | if (!key || !secret){
23 | throw 'Mintpal: Error. API key and secret required';
24 | }
25 |
26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar`
27 | paramString = Object.keys(parameters).sort().map(function(param){
28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
29 | }).join('&');
30 |
31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
32 |
33 | return {
34 | Key: key,
35 | Sign: signature
36 | };
37 | };
38 | }
39 |
40 | // If a site uses non-trusted SSL certificates, set this value to false
41 | Mintpal.STRICT_SSL = true;
42 |
43 | // Helper methods
44 | function joinCurrencies(currencyA, currencyB){
45 | return currencyA + '_' + currencyB;
46 | }
47 |
48 | // Prototype
49 | Mintpal.prototype = {
50 | constructor: Mintpal,
51 |
52 | // Make an API request
53 | _request: function(options, callback){
54 | if (!('headers' in options)){
55 | options.headers = {};
56 | }
57 |
58 | options.headers['User-Agent'] = USER_AGENT;
59 | options.json = true;
60 | options.strictSSL = Mintpal.STRICT_SSL;
61 |
62 | request(options, function(err, response, body) {
63 | callback(err, body);
64 | });
65 |
66 | return this;
67 | },
68 |
69 | // Make a public API request
70 | _public: function(parameters, callback){
71 | var options = {
72 | method: 'GET',
73 | url: PUBLIC_API_URL,
74 | qs: parameters
75 | };
76 |
77 | return this._request(options, callback);
78 | },
79 |
80 | // Make a private API request
81 | _private: function(parameters, callback){
82 | var options;
83 |
84 | parameters.nonce = nonce();
85 | options = {
86 | method: 'POST',
87 | url: PRIVATE_API_URL,
88 | form: parameters,
89 | headers: this._getPrivateHeaders(parameters)
90 | };
91 |
92 | return this._request(options, callback);
93 | },
94 |
95 |
96 | /////
97 |
98 |
99 | // PUBLIC METHODS
100 |
101 | getTicker: function(callback){
102 | var options = {
103 | method: 'GET',
104 | url: PUBLIC_API_URL + '/summary',
105 | qs: null
106 | };
107 |
108 | return this._request(options, callback);
109 | },
110 |
111 | getBuyOrderBook: function(currencyA, currencyB, callback){
112 | var options = {
113 | method: 'GET',
114 | url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY',
115 | qs: null
116 | };
117 |
118 | return this._request(options, callback);
119 | },
120 |
121 | getOrderBook: function(currencyA, currencyB, callback){
122 | var parameters = {
123 | command: 'returnOrderBook',
124 | currencyPair: joinCurrencies(currencyA, currencyB)
125 | };
126 |
127 | return this._public(parameters, callback);
128 | },
129 |
130 | getTradeHistory: function(currencyA, currencyB, callback){
131 | var parameters = {
132 | command: 'returnTradeHistory',
133 | currencyPair: joinCurrencies(currencyA, currencyB)
134 | };
135 |
136 | return this._public(parameters, callback);
137 | },
138 |
139 |
140 | /////
141 |
142 |
143 | // PRIVATE METHODS
144 |
145 | myBalances: function(callback){
146 | var parameters = {
147 | command: 'returnBalances'
148 | };
149 |
150 | return this._private(parameters, callback);
151 | },
152 |
153 | myOpenOrders: function(currencyA, currencyB, callback){
154 | var parameters = {
155 | command: 'returnOpenOrders',
156 | currencyPair: joinCurrencies(currencyA, currencyB)
157 | };
158 |
159 | return this._private(parameters, callback);
160 | },
161 |
162 | myTradeHistory: function(currencyA, currencyB, callback){
163 | var parameters = {
164 | command: 'returnTradeHistory',
165 | currencyPair: joinCurrencies(currencyA, currencyB)
166 | };
167 |
168 | return this._private(parameters, callback);
169 | },
170 |
171 | buy: function(currencyA, currencyB, rate, amount, callback){
172 | var parameters = {
173 | command: 'buy',
174 | currencyPair: joinCurrencies(currencyA, currencyB),
175 | rate: rate,
176 | amount: amount
177 | };
178 |
179 | return this._private(parameters, callback);
180 | },
181 |
182 | sell: function(currencyA, currencyB, rate, amount, callback){
183 | var parameters = {
184 | command: 'sell',
185 | currencyPair: joinCurrencies(currencyA, currencyB),
186 | rate: rate,
187 | amount: amount
188 | };
189 |
190 | return this._private(parameters, callback);
191 | },
192 |
193 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){
194 | var parameters = {
195 | command: 'cancelOrder',
196 | currencyPair: joinCurrencies(currencyA, currencyB),
197 | orderNumber: orderNumber
198 | };
199 |
200 | return this._private(parameters, callback);
201 | },
202 |
203 | withdraw: function(currency, amount, address, callback){
204 | var parameters = {
205 | command: 'withdraw',
206 | currency: currency,
207 | amount: amount,
208 | address: address
209 | };
210 |
211 | return this._private(parameters, callback);
212 | }
213 | };
214 |
215 | return Mintpal;
216 | }();
217 |
--------------------------------------------------------------------------------
/libs/apiBittrex.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var nonce = require('nonce');
3 |
4 | module.exports = function() {
5 | 'use strict';
6 |
7 | // Module dependencies
8 |
9 | // Constants
10 | var version = '0.1.0',
11 | PUBLIC_API_URL = 'https://bittrex.com/api/v1/public',
12 | PRIVATE_API_URL = 'https://bittrex.com/api/v1/market',
13 | USER_AGENT = 'nomp/node-open-mining-portal'
14 |
15 | // Constructor
16 | function Bittrex(key, secret){
17 | // Generate headers signed by this user's key and secret.
18 | // The secret is encapsulated and never exposed
19 | this._getPrivateHeaders = function(parameters){
20 | var paramString, signature;
21 |
22 | if (!key || !secret){
23 | throw 'Bittrex: Error. API key and secret required';
24 | }
25 |
26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar`
27 | paramString = Object.keys(parameters).sort().map(function(param){
28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
29 | }).join('&');
30 |
31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex');
32 |
33 | return {
34 | Key: key,
35 | Sign: signature
36 | };
37 | };
38 | }
39 |
40 | // If a site uses non-trusted SSL certificates, set this value to false
41 | Bittrex.STRICT_SSL = true;
42 |
43 | // Helper methods
44 | function joinCurrencies(currencyA, currencyB){
45 | return currencyA + '-' + currencyB;
46 | }
47 |
48 | // Prototype
49 | Bittrex.prototype = {
50 | constructor: Bittrex,
51 |
52 | // Make an API request
53 | _request: function(options, callback){
54 | if (!('headers' in options)){
55 | options.headers = {};
56 | }
57 |
58 | options.headers['User-Agent'] = USER_AGENT;
59 | options.json = true;
60 | options.strictSSL = Bittrex.STRICT_SSL;
61 |
62 | request(options, function(err, response, body) {
63 | callback(err, body);
64 | });
65 |
66 | return this;
67 | },
68 |
69 | // Make a public API request
70 | _public: function(parameters, callback){
71 | var options = {
72 | method: 'GET',
73 | url: PUBLIC_API_URL,
74 | qs: parameters
75 | };
76 |
77 | return this._request(options, callback);
78 | },
79 |
80 | // Make a private API request
81 | _private: function(parameters, callback){
82 | var options;
83 |
84 | parameters.nonce = nonce();
85 | options = {
86 | method: 'POST',
87 | url: PRIVATE_API_URL,
88 | form: parameters,
89 | headers: this._getPrivateHeaders(parameters)
90 | };
91 |
92 | return this._request(options, callback);
93 | },
94 |
95 |
96 | /////
97 |
98 |
99 | // PUBLIC METHODS
100 |
101 | getTicker: function(callback){
102 | var options = {
103 | method: 'GET',
104 | url: PUBLIC_API_URL + '/getmarketsummaries',
105 | qs: null
106 | };
107 |
108 | return this._request(options, callback);
109 | },
110 |
111 | // getBuyOrderBook: function(currencyA, currencyB, callback){
112 | // var options = {
113 | // method: 'GET',
114 | // url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY',
115 | // qs: null
116 | // };
117 |
118 | // return this._request(options, callback);
119 | // },
120 |
121 | getOrderBook: function(currencyA, currencyB, callback){
122 | var parameters = {
123 | market: joinCurrencies(currencyA, currencyB),
124 | type: 'buy',
125 | depth: '50'
126 | }
127 | var options = {
128 | method: 'GET',
129 | url: PUBLIC_API_URL + '/getorderbook',
130 | qs: parameters
131 | }
132 |
133 | return this._request(options, callback);
134 | },
135 |
136 | getTradeHistory: function(currencyA, currencyB, callback){
137 | var parameters = {
138 | command: 'returnTradeHistory',
139 | currencyPair: joinCurrencies(currencyA, currencyB)
140 | };
141 |
142 | return this._public(parameters, callback);
143 | },
144 |
145 |
146 | /////
147 |
148 |
149 | // PRIVATE METHODS
150 |
151 | myBalances: function(callback){
152 | var parameters = {
153 | command: 'returnBalances'
154 | };
155 |
156 | return this._private(parameters, callback);
157 | },
158 |
159 | myOpenOrders: function(currencyA, currencyB, callback){
160 | var parameters = {
161 | command: 'returnOpenOrders',
162 | currencyPair: joinCurrencies(currencyA, currencyB)
163 | };
164 |
165 | return this._private(parameters, callback);
166 | },
167 |
168 | myTradeHistory: function(currencyA, currencyB, callback){
169 | var parameters = {
170 | command: 'returnTradeHistory',
171 | currencyPair: joinCurrencies(currencyA, currencyB)
172 | };
173 |
174 | return this._private(parameters, callback);
175 | },
176 |
177 | buy: function(currencyA, currencyB, rate, amount, callback){
178 | var parameters = {
179 | command: 'buy',
180 | currencyPair: joinCurrencies(currencyA, currencyB),
181 | rate: rate,
182 | amount: amount
183 | };
184 |
185 | return this._private(parameters, callback);
186 | },
187 |
188 | sell: function(currencyA, currencyB, rate, amount, callback){
189 | var parameters = {
190 | command: 'sell',
191 | currencyPair: joinCurrencies(currencyA, currencyB),
192 | rate: rate,
193 | amount: amount
194 | };
195 |
196 | return this._private(parameters, callback);
197 | },
198 |
199 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){
200 | var parameters = {
201 | command: 'cancelOrder',
202 | currencyPair: joinCurrencies(currencyA, currencyB),
203 | orderNumber: orderNumber
204 | };
205 |
206 | return this._private(parameters, callback);
207 | },
208 |
209 | withdraw: function(currency, amount, address, callback){
210 | var parameters = {
211 | command: 'withdraw',
212 | currency: currency,
213 | amount: amount,
214 | address: address
215 | };
216 |
217 | return this._private(parameters, callback);
218 | }
219 | };
220 |
221 | return Bittrex;
222 | }();
223 |
--------------------------------------------------------------------------------
/website/static/stat_tracker.js:
--------------------------------------------------------------------------------
1 | //The stat object to hold everything we are tracking
2 | var stats = {};
3 |
4 | //Gets the desired pool stats stored in our cache
5 | var getPoolStats = function(key) {
6 | return stats['p_' + key];
7 | }
8 |
9 | //Gets the desired worker stats stored in our cache
10 | var getWorkerStats = function(address) {
11 | return stats['w_' + address];
12 | }
13 |
14 | //Adds a worker to the stat tracker
15 | var addWorkerToTracker = function(statData, workerData, address, callback) {
16 | if (stats['w_' + address]) {
17 | updateWorkerData(statData, workerData, address, callback);
18 | } else {
19 | buildWorkerData(statData, workerData, address, callback);
20 | }
21 | }
22 |
23 | //Adds a pool to the stat tracker
24 | var addPoolToTracker = function(poolData, poolName, callback) {
25 | if (stats['p_' + poolName]) {
26 | updatePoolData(poolData, poolName, callback);
27 | } else {
28 | buildPoolData(poolData, poolName, callback);
29 | }
30 | }
31 |
32 | /*
33 | Updates the stat cache at the given key.
34 | @param key the stat key to update
35 | @param value the value to update stat with
36 | @param index the index in our stat object to set value for
37 | */
38 | var update = function(key, value, index = 0) {
39 | var stats = stats[key];
40 | if (stats) {
41 | var statsValues = stats.values[index];
42 | if (statsValues) {
43 | statsValues.shift();
44 | statsValues.push(value);
45 | }
46 | }
47 | }
48 |
49 | //builds the initial stat data object for a worker
50 | var buildWorkerData = function(statData, workerData, address, callback = null) {
51 | if (!address || !workerData) {
52 | return;
53 | }
54 | var account = {
55 | paid: workerData.paid,
56 | balance: workerData.balances,
57 | hashrate: 0,
58 | poolHashrate: 0,
59 | shares: workerData.totalShares,
60 | currRoundShares: 0,
61 | symbol: '',
62 | pool: '',
63 | poolSize: 0,
64 | currRoundPoolShares: 0,
65 | invalidShares: 0,
66 | miners: {}
67 | };
68 | $.getJSON('/api/stats', function(data) {
69 | for (var p in data.pools) {
70 | for (var w in data.pools[p].workers) {
71 | var worker = getWorkerNameFromAddress(w);
72 | if (w.split(".")[0] === _miner) {
73 | var a = account.miners[w] = (account.miners[worker] || {
74 | key: worker,
75 | paid: data.pools[p].workers[w].paid,
76 | balance: data.pools[p].workers[w].paid,
77 | hashrate: [],
78 | validShares: data.pools[p].workers[w].shares,
79 | currRoundShares: data.pools[p].workers[w].currRoundShares,
80 | invalidShares: data.pools[p].workers[w].invalidshares
81 | });
82 | account.invalidShares += data.pools[p].workers[w].invalidshares;
83 | account.currRoundShares += data.pools[p].workers[w].currRoundShares;
84 | account.hashrate += data.pools[p].workers[w].hashrate;
85 | if (account.symbol.length < 1) {
86 | account.symbol = data.pools[p].symbol;
87 | account.poolSize = data.pools[p].workers ? Object.keys(data.pools[p].workers).length : 0;
88 | account.pool = p;
89 | }
90 | }
91 | }
92 | }
93 | if(data.pools[account.pool] && data.pools[account.pool].workers){
94 | for (var w in data.pools[account.pool].workers) {
95 | account.poolHashrate += data.pools[account.pool].workers[w].hashrate;
96 | account.currRoundPoolShares += data.pools[account.pool].workers[w].currRoundShares;
97 | }
98 | }
99 | for (var w in workerData.history) {
100 | var worker = getWorkerNameFromAddress(w);
101 | var a = account.miners[w] = (account.miners[worker] || {
102 | key: worker,
103 | paid: 0,
104 | balance: 0,
105 | hashrate: [],
106 | validShares: 0,
107 | currRoundShares: 0,
108 | invalidShares: 0
109 | });
110 | for (var wh in workerData.history[w]) {
111 | a.hashrate.push([workerData.history[w][wh].time * 1000, workerData.history[w][wh].hashrate]);
112 | }
113 | }
114 | var key = 'w_' + address;
115 | stats[key] = account;
116 | if (callback != null) {
117 | callback();
118 | }
119 | });
120 | }
121 |
122 | //builds the initial stat data object for a pool
123 | var buildPoolData = function(statData, poolName, callback = null) {
124 | if (!poolName || !statData) {
125 | return;
126 | }
127 | $.getJSON('/api/pool_stats', function(data) {
128 | var pool = {
129 | hashrate: [],
130 | averagedHashrate: [],
131 | workers: [],
132 | averagedWorkers: [],
133 | blocks: []
134 | };
135 | var totalHashrate = 0;
136 | var totalWorkers = 0;
137 | var count = 0;
138 | for (var i = 0; i < statData.length; i++) {
139 | var time = statData[i].time * 1000;
140 | if (!statData[i].pools) {
141 | continue;
142 | }
143 | if (poolName in statData[i].pools) {
144 | var hash = statData[i].pools[poolName].hashrate;
145 | var workers = statData[i].pools[poolName].workerCount;
146 | totalHashrate += hash;
147 | totalWorkers += workers;
148 | count++;
149 | var averaged = (totalHashrate > 0 && count > 1) ? totalHashrate / count : hash;
150 | var averagedWorkers = (totalWorkers > 0 && count > 1) ? totalWorkers / count : workers;
151 | pool.hashrate.push([time, hash]);
152 | pool.averagedHashrate.push([time, averaged]);
153 | pool.workers.push([time, workers]);
154 | pool.averagedWorkers.push([time, averagedWorkers]);
155 | pool.blocks.push([time, statData[i].pools[poolName].blocks.pending])
156 | } else {
157 | pool.hashrate.push([time, 0]);
158 | pool.workers.push([time, 0]);
159 | pool.averagedWorkers.push([time, 0]);
160 | pool.blocks.push([time, 0])
161 | }
162 | }
163 | var key = 'p_' + poolName;
164 | stats[key] = pool;
165 | if (callback != null) {
166 | callback();
167 | }
168 | });
169 | }
170 |
171 | //updates stat data objects for pools stored within the cache
172 | var updatePoolData = function(statData, poolName, callback = null) {
173 | var pool = stats['p_' + poolName];
174 | if (pool) {
175 | var time = statData.time * 1000;
176 | if (poolName in statData.pools) {
177 | var hash = statData.pools[poolName].hashrate;
178 | pool.hashrate.push([time, hash]);
179 | pool.averagedHashrate.push([time, pool.hashrate.reduce(function(a, b) {
180 | return a[1] + b[1];
181 | }) / pool.hashrate.length]);
182 | pool.workers.push([time, statData.pools[poolName].workerCount]);
183 | pool.blocks.push([time, statData.pools[poolName].blocks.pending])
184 | } else {
185 | pool.hashrate.push([time, 0]);
186 | pool.workers.push([time, 0]);
187 | pool.blocks.push([time, 0])
188 | }
189 | if (callback != null) {
190 | callback(pool);
191 | }
192 | } else {
193 | buildPoolData(statData, poolName, callback);
194 | }
195 | }
196 |
197 | //updates stat data objects for workers stored within the cache
198 | var updateWorkerData = function(statData, workerData, address, callback = null) {
199 | //TODO
200 | }
201 |
202 | function getWorkerNameFromAddress(w) {
203 | var worker = w;
204 | if (w.split(".").length > 1) {
205 | worker = w.split(".")[1];
206 | if (worker == null || worker.length < 1) {
207 | worker = "noname";
208 | }
209 | } else {
210 | worker = "noname";
211 | }
212 | return worker;
213 | }
214 |
--------------------------------------------------------------------------------
/website/static/nvd3.css:
--------------------------------------------------------------------------------
1 | .chartWrap{margin:0;padding:0;overflow:hidden}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 250ms linear;-moz-transition:opacity 250ms linear;-webkit-transition:opacity 250ms linear;transition-delay:250ms;-moz-transition-delay:250ms;-webkit-transition-delay:250ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{position:absolute;pointer-events:none}svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:block;width:100%;height:100%}svg text{font:400 12px Arial}svg .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .disabled circle{fill-opacity:0}.nvd3 .nv-axis{pointer-events:none}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-bars .negative rect{zfill:brown}.nvd3 .nv-bars rect{zfill:#4682b4;fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups path.nv-line{fill:none;stroke-width:1.5px}.nvd3 .nv-groups path.nv-line.nv-thin-line{stroke-width:1px}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3 .nv-line.hover path{stroke-width:6px}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}.nvd3 .nv-distribution{pointer-events:none}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:4px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel{font-weight:700}.nvd3.nv-historicalStockChart .nv-dragTarget{fill-opacity:0;stroke:none;cursor:move}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-indentedtree .name{margin-left:5px}.nvd3.nv-indentedtree .clickable{color:#08C;cursor:pointer}.nvd3.nv-indentedtree span.clickable:hover{color:#005580;text-decoration:underline}.nvd3.nv-indentedtree .nv-childrenCount{display:inline-block;margin-left:5px}.nvd3.nv-indentedtree .nv-treeicon{cursor:pointer}.nvd3.nv-indentedtree .nv-treeicon.nv-folded{cursor:pointer}.nvd3 .background path{fill:none;stroke:#ccc;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke:#4682b4;stroke-opacity:.7}.nvd3 .brush .extent{fill-opacity:.3;stroke:#fff;shape-rendering:crispEdges}.nvd3 .axis line,.axis path{fill:none;stroke:#000;shape-rendering:crispEdges}.nvd3 .axis text{text-shadow:0 1px 0 #fff}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc}
--------------------------------------------------------------------------------
/libs/api.js:
--------------------------------------------------------------------------------
1 | var redis = require('redis');
2 | var async = require('async');
3 |
4 | const functions = require('./functions.js');
5 |
6 | /*var JSONStream = require('JSONStream');
7 | var es = require('event-stream');*/
8 |
9 | var stats = require('./stats.js');
10 |
11 |
12 | const loggerFactory = require('./logger.js');
13 |
14 | const logger = loggerFactory.getLogger('Api', 'system');
15 |
16 |
17 |
18 | module.exports = function(portalConfig, poolConfigs) {
19 |
20 | var _this = this;
21 |
22 | var portalStats = this.stats = new stats(portalConfig, poolConfigs);
23 |
24 | this.liveStatConnections = {};
25 |
26 | this.handleApiRequest = function(req, res, next) {
27 | switch (req.params.method) {
28 | case 'stats':
29 | res.header('Content-Type', 'application/json');
30 | res.end(portalStats.statsString);
31 | return;
32 | case 'getblocksstats':
33 | portalStats.getBlocks(function(data){
34 | res.header('Content-Type', 'application/json');
35 | res.end(JSON.stringify(data));
36 | });
37 | break;
38 | case 'payments':
39 | var poolBlocks = [];
40 | for(var pool in portalStats.stats.pools) {
41 | poolBlocks.push({name: pool, pending: portalStats.stats.pools[pool].pending, payments: portalStats.stats.pools[pool].payments});
42 | }
43 | res.header('Content-Type', 'application/json');
44 | res.end(JSON.stringify(poolBlocks));
45 | return;
46 | case 'worker_stats':
47 | res.header('Content-Type', 'application/json');
48 | if (req.url.indexOf("?") > 0) {
49 | var url_parms = req.url.split("?");
50 | if (url_parms.length > 0) {
51 | var history = {};
52 | var workers = {};
53 | var address = url_parms[1] || null;
54 | //res.end(portalStats.getWorkerStats(address));
55 | if (address != null && address.length > 0) {
56 | // make sure it is just the miners address
57 | address = address.split(".")[0];
58 | // get miners balance along with worker balances
59 | portalStats.getBalanceByAddress(address, function(balances) {
60 |
61 | // get current round share total
62 | portalStats.getTotalSharesByAddress(address, function(shares) {
63 |
64 | var totalHash = parseFloat(0.0);
65 | var totalHeld = parseFloat(0.0);
66 | var totalShares = shares;
67 | var networkSols = 0;
68 |
69 | for (var h in portalStats.statHistory) {
70 | for (var pool in portalStats.statHistory[h].pools) {
71 | for (var w in portalStats.statHistory[h].pools[pool].workers) {
72 | if (w.startsWith(address)) {
73 | if (history[w] == null) {
74 | history[w] = [];
75 | }
76 | if (portalStats.statHistory[h].pools[pool].workers[w].hashrate) {
77 | history[w].push({
78 | time: portalStats.statHistory[h].time,
79 | hashrate: portalStats.statHistory[h].pools[pool].workers[w].hashrate
80 | });
81 | }
82 | }
83 | }
84 | // order check...
85 | //console.log(portalStats.statHistory[h].time);
86 | }
87 | }
88 |
89 | for (var pool in portalStats.stats.pools) {
90 | for (var w in portalStats.stats.pools[pool].workers) {
91 |
92 | if (w.startsWith(address)) {
93 |
94 | // console.log('>>>>BALANCES>>>>: ' + JSON.stringify(balances));
95 |
96 | workers[w] = portalStats.stats.pools[pool].workers[w];
97 |
98 | for (var b in balances.balances) {
99 | if (w == balances.balances[b].worker) {
100 | workers[w].paid = balances.balances[b].paid;
101 | workers[w].balance = balances.balances[b].balance;
102 | workers[w].immature = balances.balances[b].immature;
103 | }
104 | }
105 | workers[w].balance = (workers[w].balance || 0);
106 | workers[w].immature = (workers[w].immature || 0);
107 | workers[w].paid = (workers[w].paid || 0);
108 |
109 | // console.log('workers[w].immature: '+workers[w].immature);
110 |
111 | totalHash += portalStats.stats.pools[pool].workers[w].hashrate;
112 | networkSols = portalStats.stats.pools[pool].poolStats.networkSols;
113 |
114 | }
115 |
116 | }
117 |
118 | }
119 | res.end(JSON.stringify({
120 | miner: address,
121 | totalHash: totalHash,
122 | totalShares: totalShares,
123 | networkSols: networkSols,
124 |
125 | immature: (balances.totalImmature * 100000000),
126 | balance: balances.totalHeld,
127 | paid: balances.totalPaid,
128 |
129 | workers: workers,
130 | history: history
131 | }));
132 |
133 | });
134 |
135 |
136 | });
137 | } else {
138 | res.end(JSON.stringify({
139 | result: "error"
140 | }));
141 | }
142 | } else {
143 | res.end(JSON.stringify({
144 | result: "error"
145 | }));
146 | }
147 | } else {
148 | res.end(JSON.stringify({
149 | result: "error"
150 | }));
151 | }
152 | return;
153 | case 'pool_fees':
154 | res.header('Content-Type', 'application/json');
155 |
156 | /* leshacat code :) */
157 | var o = { pools : [] }; // empty Object
158 |
159 | for (var pool in poolConfigs) {
160 |
161 | var ttotal = 0.0;
162 |
163 | var rewardRecipients = portalStats.stats.pools[pool].rewardRecipients || {};
164 | for (var r in rewardRecipients) {
165 | ttotal += rewardRecipients[r];
166 | }
167 |
168 |
169 | var intSec = poolConfigs[pool].paymentProcessing.paymentInterval || 0;
170 | var intMinPymt = poolConfigs[pool].paymentProcessing.minimumPayment || 0;
171 | var strSchema = poolConfigs[pool].paymentProcessing.schema || "PROP";
172 |
173 | tmpStr = functions.secToDHMSStr(intSec);
174 |
175 | o.pools.push({"coin":pool, "fee": ttotal, "payoutscheme":strSchema, "interval":intSec, "intervalstr":tmpStr, "minimum": intMinPymt}); //
176 |
177 | }
178 | res.end(JSON.stringify(o));
179 |
180 | return;
181 | case 'pool_stats':
182 | res.header('Content-Type', 'application/json');
183 |
184 | res.end(JSON.stringify(portalStats.statPoolHistory));
185 |
186 | return;
187 | case 'live_stats':
188 | res.writeHead(200, {
189 | 'Content-Type': 'text/event-stream',
190 | 'Cache-Control': 'no-cache',
191 | 'Connection': 'keep-alive'
192 | });
193 | res.write('\n');
194 | var uid = Math.random().toString();
195 | _this.liveStatConnections[uid] = res;
196 | res.flush();
197 | req.on("close", function() {
198 | delete _this.liveStatConnections[uid];
199 | });
200 | default:
201 | next();
202 | }
203 | };
204 |
205 | Object.filter = (obj, predicate) =>
206 | Object.keys(obj)
207 | .filter( key => predicate(obj[key]) )
208 | .reduce( (res, key) => (res[key] = obj[key], res), {} );
209 |
210 | this.handleAdminApiRequest = function(req, res, next) {
211 | switch (req.params.method) {
212 | case 'pools':
213 | {
214 | res.end(JSON.stringify({
215 | result: poolConfigs
216 | }));
217 | return;
218 | }
219 | default:
220 | next();
221 | }
222 | };
223 |
224 | };
225 |
--------------------------------------------------------------------------------
/website/static/miner_stats.js:
--------------------------------------------------------------------------------
1 | var workerHashrateData;
2 | var workerHashrateChart;
3 | var workerHistoryMax = 160;
4 |
5 | var statData;
6 | var totalHash;
7 | var totalImmature;
8 | var totalBal;
9 | var totalPaid;
10 | var totalShares;
11 | var alerted = false;
12 | var shareGage;
13 | var invalidGage;
14 | var workerGage;
15 | var hashGage;
16 |
17 | function getWorkerNameFromAddress(w) {
18 | var worker = w;
19 | if (w.split(".").length > 1) {
20 | worker = w.split(".")[1];
21 | if (worker == null || worker.length < 1) {
22 | worker = "noname";
23 | }
24 | } else {
25 | worker = "noname";
26 | }
27 | return worker;
28 | }
29 |
30 | function displayCharts() {
31 | var stats = getWorkerStats(_miner);
32 | shareGage = new JustGage({
33 | id: "gauge",
34 | value: stats.currRoundShares > 0 ? Math.floor((stats.currRoundShares / stats.currRoundPoolShares) * 100) : 0,
35 | min: 0,
36 | max: 100,
37 | symbol: '%',
38 | pointer: true,
39 | counter: true,
40 | decimals: 2,
41 | pointerOptions: {
42 | toplength: -15,
43 | bottomlength: 10,
44 | bottomwidth: 12,
45 | color: '#8e8e93',
46 | stroke: '#ffffff',
47 | stroke_width: 3,
48 | stroke_linecap: 'round'
49 | },
50 | title: "Shares This Round",
51 | gaugeWidthScale: 0.6,
52 | levelColors:["#e8e84c", "#6cdb5e"],
53 | levelColorsGradient: true
54 | });
55 |
56 | var tmpInt = Math.min((((10000 * stats.shares / (stats.shares + stats.invalidShares)) / 100)), 100);
57 | var tmpInt2 = (100 - tmpInt) || 0;
58 |
59 | invalidGage = new JustGage({
60 | id: "validShare",
61 | value: tmpInt2,
62 | min: 0,
63 | max: 100,
64 | symbol: '%',
65 | pointer: true,
66 | counter: true,
67 | decimals: 2,
68 | pointerOptions: {
69 | toplength: -15,
70 | bottomlength: 10,
71 | bottomwidth: 12,
72 | color: '#8e8e93',
73 | stroke: '#ffffff',
74 | stroke_width: 3,
75 | stroke_linecap: 'round'
76 | },
77 | title: "Invalid Shares",
78 | gaugeWidthScale: 0.6,
79 | levelColors:["#f9a42c", "#f21f10"],
80 | levelColorsGradient: true
81 | });
82 | workerGage= new JustGage({
83 | id: "workerDominance",
84 | value: stats.miners ? (Object.keys(stats.miners).length / stats.poolSize) * 100 : 0,
85 | min: 0,
86 | max: 100,
87 | symbol: '%',
88 | pointer: true,
89 | counter: true,
90 | decimals: 2,
91 | pointerOptions: {
92 | toplength: -15,
93 | bottomlength: 10,
94 | bottomwidth: 12,
95 | color: '#8e8e93',
96 | stroke: '#ffffff',
97 | stroke_width: 3,
98 | stroke_linecap: 'round'
99 | },
100 | title: "Worker Dominance",
101 | gaugeWidthScale: 0.6,
102 | levelColors:["#e8e84c", "#6cdb5e"],
103 | levelColorsGradient: true
104 | });
105 | var high = 0;
106 | console.log(stats.hashrate);
107 | hashGage = new JustGage({
108 | id: "hashDominance",
109 | value: stats.hashrate > 0 ? (stats.hashrate / stats.poolHashrate) * 100 : 0,
110 | min: 0,
111 | max: 100,
112 | symbol: '%',
113 | title: "Hashrate Dominance",
114 | levelColors:["#e8e84c", "#6cdb5e"],
115 | levelColorsGradient: true,
116 | pointer: true,
117 | counter: true,
118 | decimals: 2,
119 | pointerOptions: {
120 | toplength: -15,
121 | bottomlength: 10,
122 | bottomwidth: 12,
123 | color: '#8e8e93',
124 | stroke: '#ffffff',
125 | stroke_width: 3,
126 | stroke_linecap: 'round'
127 | },
128 | gaugeWidthScale: 0.6
129 | });
130 | var maxScale = 0;
131 | var label = 'H/s';
132 | for (var w in stats.miners) {
133 | var pair = getReadableHashRatePair(Math.max.apply(null, stats.miners[w].hashrate.map(x => x[1])));
134 | var i = pair[2];
135 | if (maxScale < i) {
136 | maxScale = i;
137 | label = pair[1];
138 | }
139 | }
140 | var dataset = [];
141 | for (var d in stats.miners) {
142 | var data = stats.miners[d];
143 | var color = getRandomPastelColor();
144 | var o = {
145 | label: data.key,
146 | fill: false,
147 | data: data.hashrate.map(x => {
148 | return {
149 | t: x[0],
150 | y: getScaledHashrate(x[1], i)
151 | }
152 | }),
153 | borderWidth: 2,
154 | backgroundColor: color,
155 | borderColor: color
156 | };
157 | dataset.push(o);
158 | }
159 |
160 | workerHashrateChart = createDefaultLineChart(
161 | document.getElementById("workerHashChart").getContext('2d'),
162 | dataset,
163 | 'Time',
164 | label
165 | );
166 | }
167 |
168 | function updateStats() {
169 |
170 | var stats = getWorkerStats(_miner);
171 |
172 | totalHash = stats.hashrate;
173 | totalShares = stats.totalShares;
174 |
175 | // update miner stats
176 | $("#statsHashrate").text(getReadableHashRateString(totalHash));
177 | $("#statsHashrateAvg").text(getReadableHashRateString(calculateAverageHashrate(null)));
178 |
179 | /* $("#statsTotalImmature").text(totalImmature);
180 | $("#statsTotalBal").text(totalBal);
181 | $("#statsTotalPaid").text(totalPaid);*/
182 |
183 | }
184 |
185 | function updateWorkerStats() {
186 | var stats = getWorkerStats(_miner);
187 | // update worker stats
188 | var i = 0;
189 | for (var w in stats.miners) {
190 | i++;
191 | var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, '');
192 | var saneWorkerName = getWorkerNameFromAddress(w);
193 | console.log(stats.miners[w]);
194 | $("#statsHashrate" + htmlSafeWorkerName).text(getReadableHashRateString(stats.miners[w].hashrate[stats.miners[w].hashrate.length - 1] || 0));
195 | $("#statsHashrateAvg" + htmlSafeWorkerName).text(getReadableHashRateString(calculateAverageHashrate(saneWorkerName)));
196 |
197 | /* $("#statsTotalImmature").text(totalImmature);
198 | $("#statsTotalBal").text(totalBal);
199 | $("#statsTotalPaid").text(totalPaid);*/
200 |
201 | }
202 | }
203 |
204 | function addWorkerToDisplay(name, htmlSafeName, workerObj) {
205 | var htmlToAdd = "";
206 | htmlToAdd = '';
207 | htmlToAdd += '
';
208 | htmlToAdd += '
' + getReadableHashRateString(workerObj.hashrate[workerObj.hashrate.length - 1][1] || 0) + ' (Now)
';
209 | htmlToAdd += '
' + getReadableHashRateString(calculateAverageHashrate(name)) + ' (Avg)
';
210 | htmlToAdd += '
';
211 |
212 | $("#boxesWorkers").html($("#boxesWorkers").html() + htmlToAdd);
213 |
214 | }
215 |
216 | function calculateAverageHashrate(worker) {
217 | var stats = getWorkerStats(_miner);
218 | var count = 0;
219 | var total = 1;
220 | var avg = 0;
221 | for (w in stats.miners) {
222 | count = 0;
223 | for (var ii = 0; ii < stats.miners[w].hashrate.length; ii++) {
224 | if (worker == null || stats.miners[w].key === worker) {
225 | count++;
226 | avg += parseFloat(stats.miners[w].hashrate[ii][1]);
227 | }
228 | }
229 | if (count > total)
230 | total = count;
231 | }
232 | avg = avg / total;
233 | return avg;
234 | }
235 |
236 |
237 | function rebuildWorkerDisplay() {
238 | var stats = getWorkerStats(_miner);
239 | $("#boxesWorkers").html("");
240 | var i = 0;
241 | for (var w in stats.miners) {
242 | i++;
243 | var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, '');
244 | var saneWorkerName = getWorkerNameFromAddress(w);
245 | addWorkerToDisplay(saneWorkerName, htmlSafeWorkerName, stats.miners[w]);
246 | }
247 | }
248 |
249 |
250 | // grab initial stats
251 | $.getJSON('/api/worker_stats?' + _miner, function(data) {
252 | if (document.hidden) return;
253 | $.getJSON('/api/pool_stats', function(statData) {
254 | addWorkerToTracker(statData, data, _miner, function(){
255 |
256 |
257 | //alert("MS> Locating worker stats for: " + _miner);
258 |
259 | var stats = getWorkerStats(_miner);
260 |
261 | statData = data;
262 |
263 | for (var w in statData.workers) {
264 | _workerCount++;
265 | }
266 |
267 | displayCharts();
268 | rebuildWorkerDisplay();
269 | updateStats();
270 |
271 | var totalPaid = statData.paid || 0;
272 | var totalBal = statData.balance || 0;
273 | var totalImmature = (statData.immature) || 0;
274 |
275 | //alert('immature: ' + totalImmature);
276 |
277 | var luckDays = statData.luckDays || "unknown";
278 |
279 | var SYMB = stats.symbol || "unknown symbol";
280 |
281 | $('#total-paid-label').append(totalPaid.toFixed(8) + ' ' + SYMB);
282 |
283 | $('#total-immature-label').append(totalImmature.toFixed(8) + ' ' + SYMB);
284 | $('#total-balance-label').append(totalBal.toFixed(8) + ' ' + SYMB);
285 |
286 | $('#total-luckdays-label').append(luckDays);
287 |
288 | });
289 | });
290 | });
291 |
292 |
293 | // live stat updates
294 | statsSource.addEventListener('message', function(e) {
295 | var stats = JSON.parse(e.data);
296 | $.getJSON('/api/worker_stats?' + _miner, function(data) {
297 | //$('#total-paid-label').empty();
298 | //$('#total-paid-label').append(total.toFixed(8) + ' ' + symbol);
299 | });
300 | });
301 |
--------------------------------------------------------------------------------