├── .editorconfig ├── LICENSE ├── README.md ├── api ├── controllers │ ├── configController.js │ └── statsController.js ├── lib │ ├── capacity.js │ ├── coinbase-api.js │ ├── dashboards │ │ ├── balances │ │ │ ├── all-the-blocks-balance.js │ │ │ ├── bhd-balance.js │ │ │ ├── bitcoin-balance.js │ │ │ ├── bitmart-balance.js │ │ │ ├── coinbase-balance.js │ │ │ ├── counterparty-balance.js │ │ │ ├── cryptoid-balance.js │ │ │ ├── ethereum-balance.js │ │ │ ├── nicehash-balance.js │ │ │ └── signum-balance.js │ │ ├── custom │ │ │ ├── colab-manager-stats-collection.js │ │ │ ├── dashboard-api.js │ │ │ └── hdpool-control.js │ │ ├── dashboard-util.js │ │ ├── dashboard.js │ │ ├── pool │ │ │ ├── foxy-pool-v2.js │ │ │ ├── hdpool.js │ │ │ ├── hpool.js │ │ │ ├── miningpoolhub.js │ │ │ ├── mpos.js │ │ │ ├── nicehash.js │ │ │ ├── node-cryptonote-pool.js │ │ │ ├── snipa-nodejs-pool.js │ │ │ └── yiimp.js │ │ └── wallets │ │ │ ├── bhd-wallet.js │ │ │ ├── bitbean-wallet.js │ │ │ ├── boom-wallet.js │ │ │ ├── burst-wallet.js │ │ │ ├── chia-wallet.js │ │ │ ├── disc-wallet.js │ │ │ ├── generic-wallet.js │ │ │ └── wallet-agent.js │ ├── miner │ │ ├── burst-proxy.js │ │ ├── chia-archiver.js │ │ ├── chia-farmer.js │ │ ├── chia-miner.js │ │ ├── chia-plotter.js │ │ ├── creep-miner.js │ │ ├── group.js │ │ ├── miner-manager.js │ │ ├── miner-util.js │ │ ├── miner.js │ │ ├── rclone.js │ │ └── storj.js │ ├── nicehash-api.js │ ├── rates │ │ └── coingecko.js │ ├── services │ │ ├── mail-service.js │ │ └── problem-service.js │ ├── stats.js │ └── util.js ├── modules │ └── configModule.js └── routes.js ├── app ├── views │ └── partials │ │ ├── components │ │ ├── balances │ │ │ ├── balance-summary.html │ │ │ ├── bitcoinBalance.html │ │ │ ├── bitmartBalance.html │ │ │ ├── counterpartyBalance.html │ │ │ ├── cryptoidBalance.html │ │ │ ├── ethereumBalance.html │ │ │ └── nicehashBalance.html │ │ ├── custom │ │ │ ├── colab-manager-stats-collection.html │ │ │ ├── dashboardApi.html │ │ │ └── hdpool-control.html │ │ ├── miner │ │ │ ├── burstProxy.html │ │ │ ├── chia-archiver.html │ │ │ ├── chia-farmer.html │ │ │ ├── chia-miner.html │ │ │ ├── chia-plotter.html │ │ │ ├── creepMiner.html │ │ │ ├── minerManager.html │ │ │ ├── rclone.html │ │ │ └── storj.html │ │ ├── pools │ │ │ ├── foxy-pool.html │ │ │ ├── hdpool.html │ │ │ ├── hpool.html │ │ │ ├── miningpoolhub.html │ │ │ ├── mpos.html │ │ │ ├── nicehash.html │ │ │ ├── nodeCryptonotePools.html │ │ │ └── yiimp.html │ │ └── wallets │ │ │ └── wallet.html │ │ ├── configDashboards.html │ │ ├── configDevices.html │ │ ├── configGeneral.html │ │ └── stats.html └── web │ ├── css │ ├── bootstrap-xl.css │ ├── bootstrap-xxl.css │ ├── bootstrap.min.css │ ├── fontawesome.min.css │ ├── loading-bar.min.css │ └── main.css │ ├── font │ ├── material-design-icons │ │ ├── LICENSE.txt │ │ ├── Material-Design-Icons.eot │ │ ├── Material-Design-Icons.svg │ │ ├── Material-Design-Icons.ttf │ │ ├── Material-Design-Icons.woff │ │ └── Material-Design-Icons.woff2 │ └── roboto │ │ ├── Roboto-Bold.eot │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.eot │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.eot │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.eot │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Regular.woff │ │ ├── Roboto-Regular.woff2 │ │ ├── Roboto-Thin.eot │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Thin.woff │ │ └── Roboto-Thin.woff2 │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ └── roboto │ │ ├── Roboto-Bold.eot │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.eot │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.eot │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.eot │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Regular.woff │ │ ├── Roboto-Regular.woff2 │ │ ├── Roboto-Thin.eot │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Thin.woff │ │ └── Roboto-Thin.woff2 │ ├── js │ ├── angular-filter.min.js │ ├── angular-masonry.min.js │ ├── angular-ui-router.min.js │ ├── angular.min.js │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── balances │ │ │ │ ├── balance-summary.js │ │ │ │ ├── bitcoinBalance.js │ │ │ │ ├── bitmartBalance.js │ │ │ │ ├── counterpartyBalance.js │ │ │ │ ├── cryptoidBalance.js │ │ │ │ ├── ethereumBalance.js │ │ │ │ └── nicehashBalance.js │ │ │ ├── custom │ │ │ │ ├── colab-manager-stats-collection.js │ │ │ │ ├── dashboardApi.js │ │ │ │ └── hdpool-control.js │ │ │ ├── miner │ │ │ │ ├── burstProxy.js │ │ │ │ ├── chia-archiver.js │ │ │ │ ├── chia-farmer.js │ │ │ │ ├── chia-miner.js │ │ │ │ ├── chia-plotter.js │ │ │ │ ├── creepMiner.js │ │ │ │ ├── minerManager.js │ │ │ │ ├── rclone.js │ │ │ │ └── storj.js │ │ │ ├── pools │ │ │ │ ├── foxy-pool.js │ │ │ │ ├── hdpool.js │ │ │ │ ├── hpool.js │ │ │ │ ├── miningpoolhub.js │ │ │ │ ├── mpos.js │ │ │ │ ├── nicehash.js │ │ │ │ ├── nodeCryptonotePools.js │ │ │ │ └── yiimp.js │ │ │ └── wallets │ │ │ │ └── wallet.js │ │ └── controllers │ │ │ ├── configCtrl.js │ │ │ └── statsCtrl.js │ ├── bootstrap.min.js │ ├── imagesloaded.pkgd.min.js │ ├── jquery.min.js │ ├── loading-bar.min.js │ ├── masonry.pkgd.min.js │ ├── materialize.min.js │ └── moment.min.js │ └── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.svg │ ├── fa-brands-400.ttf │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.svg │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.eot │ ├── fa-solid-900.svg │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ └── fa-solid-900.woff2 ├── helpers ├── update.bat └── update.sh ├── index.html ├── main.js ├── package.json ├── process.json ├── screens └── stats.png └── startTemplate.bat /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | # editorconfig-tools is unable to ignore longs strings or urls 10 | max_line_length = 120 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # miner-monitor 2 | 3 | Miner, balance, wallet and pool monitoring software 4 | 5 | ### Screen 6 | 7 |  8 | 9 | 10 | ### Prerequisites 11 | 12 | miner-monitor requires nodejs >= 8.9.0, npm and optionally pm2 to run. 13 | 14 | miners require the miner-manager to be installed 15 | 16 | ### Installation 17 | 18 | ```sh 19 | git clone https://github.com/felixbrucker/miner-monitor 20 | cd miner-monitor 21 | npm install 22 | npm install pm2 -g 23 | ``` 24 | 25 | ### Run 26 | 27 | ```sh 28 | pm2 start process.json 29 | ``` 30 | 31 | or 32 | 33 | ```sh 34 | npm start 35 | ``` 36 | 37 | to startup on boot: 38 | 39 | ```sh 40 | pm2 save 41 | pm2 startup 42 | ``` 43 | 44 | If you are on Windows just modify startTemplate.bat file to match your preferred compile and save as start.bat to not interfere with git updates 45 | 46 | ### Update software 47 | 48 | run ``` git pull ``` 49 | 50 | ### Todos 51 | 52 | - Write Tests 53 | 54 | 55 | License 56 | ---- 57 | 58 | GNU GPLv3 (see LICENSE) 59 | -------------------------------------------------------------------------------- /api/controllers/statsController.js: -------------------------------------------------------------------------------- 1 | const Stats = require('../lib/stats'); 2 | 3 | let instance = null; 4 | 5 | function getStats(req, res, next) { 6 | res.setHeader('Content-Type', 'application/json'); 7 | if (!instance) { 8 | return res.send({entries: [], dashboardData: []}); 9 | } 10 | 11 | res.send(JSON.stringify(instance.getStats())); 12 | } 13 | 14 | function restartInterval() { 15 | instance.cleanup(); 16 | init(); 17 | } 18 | 19 | function init() { 20 | instance = new Stats(); 21 | } 22 | 23 | setTimeout(init, 2000); 24 | 25 | exports.getStats = getStats; 26 | exports.restartInterval = restartInterval; 27 | -------------------------------------------------------------------------------- /api/lib/capacity.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js'); 2 | 3 | class Capacity { 4 | static fromTiB(capacityInTiB) { 5 | return new Capacity(new BigNumber(capacityInTiB).multipliedBy(1024)); 6 | } 7 | 8 | static fromBytes(capacityInBytes) { 9 | return new Capacity(new BigNumber(capacityInBytes).dividedBy(new BigNumber(1024).pow(3))); 10 | } 11 | 12 | constructor(capacityInGib) { 13 | this.capacityInGib = new BigNumber(capacityInGib); 14 | } 15 | 16 | toTiB() { 17 | return this.capacityInGib.dividedBy(1024); 18 | } 19 | 20 | toMiB() { 21 | return this.capacityInGib.multipliedBy(1024); 22 | } 23 | 24 | toString(precision = 2) { 25 | let capacity = this.capacityInGib; 26 | let unit = 0; 27 | const units = ['GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; 28 | while (capacity.isGreaterThanOrEqualTo(1024)) { 29 | capacity = capacity.dividedBy(1024); 30 | unit += 1; 31 | } 32 | 33 | return `${capacity.toFixed(precision)} ${units[unit]}`; 34 | } 35 | } 36 | 37 | module.exports = Capacity; 38 | -------------------------------------------------------------------------------- /api/lib/coinbase-api.js: -------------------------------------------------------------------------------- 1 | const { createHmac } = require('crypto'); 2 | const axios = require('axios'); 3 | const {sleep} = require('./util') 4 | 5 | class CoinbaseApi { 6 | constructor({ 7 | apiKey, 8 | apiSecret, 9 | }) { 10 | this.apiKey = apiKey; 11 | this.apiSecret = apiSecret; 12 | this.client = axios.create({ 13 | baseURL: 'https://api.coinbase.com', 14 | }); 15 | this.client.interceptors.request.use(this._onBeforeRequestSent.bind(this), (err) => Promise.reject(err)); 16 | } 17 | 18 | async listAllAccounts({ excludeZeroBalances }) { 19 | let cursor = undefined 20 | let allAccounts = [] 21 | do { 22 | const accountsResponse = await this._listAccounts({ limit: 250, cursor }) 23 | let filteredAccounts = accountsResponse.accounts 24 | if (excludeZeroBalances) { 25 | filteredAccounts = filteredAccounts.filter(account => parseFloat(account.available_balance.value) > 0) 26 | } 27 | allAccounts = allAccounts.concat(filteredAccounts) 28 | cursor = accountsResponse.has_next ? accountsResponse.cursor : undefined 29 | if (cursor !== undefined) { 30 | await sleep(0.25) 31 | } 32 | } while (cursor !== undefined); 33 | 34 | return allAccounts 35 | } 36 | 37 | async _listAccounts({ limit = 250, cursor } = {}) { 38 | const params = { limit } 39 | if (cursor) { 40 | params.cursor = cursor 41 | } 42 | const { data } = await this.client.get('/api/v3/brokerage/accounts', { 43 | params, 44 | }); 45 | 46 | return data; 47 | } 48 | 49 | _onBeforeRequestSent(config) { 50 | if (config.isPublic) { 51 | return config; 52 | } 53 | 54 | config.headers = Object.assign(config.headers, this._getAuthenticatedRequestHeaders({ 55 | method: config.method.toUpperCase(), 56 | path: config.url, 57 | body: config.data, 58 | })); 59 | 60 | return config; 61 | } 62 | 63 | _getAuthenticatedRequestHeaders(request) { 64 | const timestamp = Math.floor(Date.now() / 1000); 65 | 66 | return { 67 | 'CB-ACCESS-KEY': this.apiKey, 68 | 'CB-ACCESS-TIMESTAMP': timestamp, 69 | 'CB-ACCESS-SIGN': this._createSignature({ timestamp, request }), 70 | }; 71 | } 72 | 73 | _createSignature({ timestamp, request }) { 74 | const hmac = createHmac('sha256', this.apiSecret); 75 | hmac.update(`${timestamp}${request.method}${request.path}`); 76 | if (request.body) { 77 | hmac.update(`${JSON.stringify(request.body)}`); 78 | } 79 | 80 | return hmac.digest('hex'); 81 | } 82 | } 83 | 84 | module.exports = CoinbaseApi; 85 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/all-the-blocks-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class AllTheBlocksBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options.dashboard.coin = options.dashboard.api_key; 15 | options = Object.assign(AllTheBlocksBalance.getDefaults(), options); 16 | super(options); 17 | } 18 | 19 | getStats() { 20 | return Object.assign(super.getStats(), { 21 | addressUrl: `https://alltheblocks.net/${this.dashboard.coin.toLowerCase()}/address/${this.dashboard.address}`, 22 | ticker: this.dashboard.ticker.toUpperCase(), 23 | coin: this.dashboard.coin, 24 | }); 25 | } 26 | 27 | async updateStats() { 28 | try { 29 | const addressData = await util.getUrl(`https://api.alltheblocks.net/${this.dashboard.coin.toLowerCase()}/address/name/${this.dashboard.address}`); 30 | const result = { 31 | balance: addressData.balance / (10 ** this.dashboard.hrModifier), 32 | }; 33 | 34 | const rates = coinGecko.getRates(this.dashboard.ticker); 35 | const rate = rates.length > 0 ? rates[0] : null; 36 | if (rate) { 37 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 38 | } 39 | this.stats = result; 40 | } catch(err) { 41 | console.error(`[${this.dashboard.name} :: AllTheBlocksBalance-API] => ${err.message}`); 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/bhd-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class BhdBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(BhdBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { 20 | addressUrl: `https://www.btchd.org/explorer/address/${this.dashboard.address}`, 21 | ticker: 'BHD', 22 | coin: 'Bitcoin HD', 23 | }); 24 | } 25 | 26 | async updateStats() { 27 | try { 28 | const data = await util.getUrl(`https://www.btchd.org/explorer/api/v2/blockchain/address/${this.dashboard.address}`); 29 | const result = { 30 | balance: parseFloat(data.balance), 31 | }; 32 | 33 | const rates = coinGecko.getRates('BHD'); 34 | const rate = rates.length > 0 ? rates[0] : null; 35 | if (rate) { 36 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 37 | } 38 | this.stats = result; 39 | } catch(err) { 40 | console.error(`[${this.dashboard.name} :: BHD-Balance-API] => ${err.message}`); 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/bitcoin-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class BitcoinBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(BitcoinBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { addr: this.dashboard.address }); 20 | } 21 | 22 | async updateStats() { 23 | try { 24 | const balanceData = await util.getUrl(`https://blockchain.info/address/${this.dashboard.address}?format=json&limit=0`); 25 | const result = { 26 | balance: balanceData['final_balance'] / 100000000, 27 | }; 28 | 29 | const rates = coinGecko.getRates('BTC'); 30 | const rate = rates.length > 0 ? rates[0] : null; 31 | if (rate) { 32 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 33 | } 34 | this.stats = result; 35 | } catch(err) { 36 | console.error(`[${this.dashboard.name} :: Bitcoin-Balance-API] => ${err.message}`); 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/bitmart-balance.js: -------------------------------------------------------------------------------- 1 | const Dashboard = require('../dashboard'); 2 | const coinGecko = require('../../rates/coingecko'); 3 | const { BitmartRestApi } = require('bitmart-api'); 4 | 5 | module.exports = class BitmartBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(BitmartBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | onInit() { 19 | const apiNameAndKeyAndSecret = this.dashboard.api_key.split(':'); 20 | if (apiNameAndKeyAndSecret.length > 3) { 21 | throw new Error('Invalid bitmart api name, key and secret given, please use the following format: api-name:api-key:api-secret'); 22 | } 23 | this.client = new BitmartRestApi( 24 | apiNameAndKeyAndSecret[0], 25 | apiNameAndKeyAndSecret[1], 26 | apiNameAndKeyAndSecret[2] 27 | ); 28 | super.onInit(); 29 | } 30 | 31 | async updateStats() { 32 | try { 33 | const walletBalances = await this.client.getWalletBalances(); 34 | walletBalances.forEach(balance => { 35 | balance.available = parseFloat(balance.available); 36 | balance.frozen = parseFloat(balance.frozen); 37 | }); 38 | const nonZeroBalances = walletBalances.filter(balance => balance.available > 0.000001 || balance.frozen > 0.000001); 39 | nonZeroBalances.forEach(balance => { 40 | const rates = coinGecko.getRates(balance.id); 41 | const rate = rates.length > 0 ? rates[0] : null; 42 | if (rate) { 43 | balance.availableFiat = parseFloat(rate.current_price) * balance.available; 44 | balance.frozenFiat = parseFloat(rate.current_price) * balance.frozen; 45 | } 46 | }); 47 | this.stats = nonZeroBalances; 48 | } catch(err) { 49 | console.error(`[${this.dashboard.name} :: Bitmart-Balance-API] => ${err.message}`); 50 | } 51 | } 52 | 53 | cleanup() { 54 | this.client.destroy(); 55 | super.cleanup(); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/coinbase-balance.js: -------------------------------------------------------------------------------- 1 | const Dashboard = require('../dashboard'); 2 | const coinGecko = require('../../rates/coingecko'); 3 | const CoinbaseApi = require('../../coinbase-api') 4 | 5 | module.exports = class CoinbaseBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(CoinbaseBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | async updateStats() { 19 | try { 20 | const accounts = await this.client.listAllAccounts({ excludeZeroBalances: true }); 21 | this.stats = accounts 22 | .map(account => ({ 23 | name: account.name, 24 | ticker: account.currency, 25 | balance: parseFloat(account.available_balance.value), 26 | })) 27 | .filter(account => (account.ticker !== 'EUR' && account.balance > 0.000001) || (account.ticker === 'EUR' && account.balance >= 0.01)) 28 | .map(account => { 29 | const rates = coinGecko.getRates(account.ticker); 30 | const rate = rates.length > 0 ? rates[0] : null; 31 | if (rate) { 32 | account.balanceFiat = parseFloat(rate.current_price) * account.balance; 33 | } 34 | 35 | return account; 36 | }); 37 | } catch(err) { 38 | console.error(`[${this.dashboard.name} :: Coinbase-Balance-API] => ${err.message}`); 39 | } 40 | } 41 | 42 | async onInit() { 43 | const apiKeySecretAndPassphrase = this.dashboard.api_key.split(':'); 44 | if (apiKeySecretAndPassphrase.length !== 2) { 45 | return console.error(`[${this.dashboard.name} :: Coinbase-Balance-API] => Invalid api key and secret key string, format is: 'api_key:api_secret'`); 46 | } 47 | this.client = new CoinbaseApi({ 48 | apiKey: apiKeySecretAndPassphrase[0], 49 | apiSecret: apiKeySecretAndPassphrase[1], 50 | }); 51 | super.onInit(); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/counterparty-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class CounterpartyBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(CounterpartyBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { addr: this.dashboard.address }); 20 | } 21 | 22 | async updateStats() { 23 | try { 24 | const balanceData = await util.getUrl(`https://xchain.io/api/balances/${this.dashboard.address}`); 25 | balanceData.data.forEach((asset) => { 26 | asset.balance = parseFloat(asset.quantity); 27 | delete asset.quantity; 28 | 29 | const rates = coinGecko.getRates(asset.asset); 30 | const rate = rates.length > 0 ? rates[0] : null; 31 | if (rate) { 32 | asset.balanceFiat = parseFloat(rate.current_price) * asset.balance; 33 | } 34 | }); 35 | this.stats = balanceData.data; 36 | } catch(err) { 37 | console.error(`[${this.dashboard.name} :: Counterparty-Balance-API] => ${err.message}`); 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/cryptoid-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class CryptoidBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(CryptoidBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { 20 | addr: this.dashboard.address, 21 | ticker: this.dashboard.ticker.toUpperCase(), 22 | }); 23 | } 24 | 25 | async updateStats() { 26 | try { 27 | const balance = await util.getUrl(`https://chainz.cryptoid.info/${this.dashboard.ticker}/api.dws?q=getbalance&a=${this.dashboard.address}${this.dashboard.api_key ? '&key=' + this.dashboard.api_key : ''}`); 28 | const result = { 29 | balance, 30 | }; 31 | 32 | const rates = coinGecko.getRates(this.dashboard.ticker); 33 | const rate = rates.length > 0 ? rates[0] : null; 34 | if (rate) { 35 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 36 | } 37 | this.stats = result; 38 | } catch(err) { 39 | console.error(`[${this.dashboard.name} :: Cryptoid-Balance-API] => ${err.message}`); 40 | } 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/ethereum-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | const blacklistedTokens = [ 6 | '0xc12d1c73ee7dc3615ba4e37e4abfdbddfa38907e', 7 | '0x2e91e3e54c5788e9fdd6a181497fdcea1de1bcc1', 8 | '0x5e888b83b7287eed4fb7da7b7d0a0d4c735d94b3', 9 | '0x79186ba0fc6fa49fd9db2f0ba34f36f8c24489c7', 10 | '0xa0ec5625a9316b9846a7f5239186f0fd1919e516', 11 | '0xa38b7ee9df79955b90cc4e2de90421f6baa83a3d', 12 | '0xab95e915c123fded5bdfb6325e35ef5515f1ea69', 13 | '0xbddab785b306bcd9fb056da189615cc8ece1d823', 14 | '0xbf52f2ab39e26e0951d2a02b49b7702abe30406a', 15 | '0x5c406d99e04b8494dc253fcc52943ef82bca7d75', 16 | '0x7379cbce70bba5a9871f97d33b391afba377e885', 17 | '0x17a10104cbc1ed155d083ead9fcf5c3440bb50e8', 18 | '0x38715ab4b9d4e00890773d7338d94778b0dfc0a8', 19 | '0x19383f024ba4c06e44d11a8b8bb7ebf87fab184c', 20 | '0xcdc94877e4164d2e915fc5e8310155d661a995f1', 21 | '0xba89375bae9b3de92442e9c037d4303a6e4fb086', 22 | '0x956f824b5a37673c6fc4a6904186cb3ba499349b', 23 | '0x7a6b87d7a874fce4c2d923b09c0e09e4936bcf57', 24 | '0x643695d282f6ba237afe27ffe0acd89a86b50d3e', 25 | ]; 26 | 27 | module.exports = class EthereumBalance extends Dashboard { 28 | 29 | static getDefaults() { 30 | return { 31 | interval: 10 * 60 * 1000, 32 | }; 33 | } 34 | 35 | constructor(options = {}) { 36 | options = Object.assign(EthereumBalance.getDefaults(), options); 37 | super(options); 38 | } 39 | 40 | getStats() { 41 | return Object.assign(super.getStats(), { addr: this.dashboard.address }); 42 | } 43 | 44 | async updateStats() { 45 | try { 46 | const balanceData = await util.getUrl(`https://api.ethplorer.io/getAddressInfo/${this.dashboard.address}?apiKey=freekey`); 47 | const result = { 48 | eth: balanceData.ETH, 49 | tokens: balanceData.tokens || [], 50 | }; 51 | result.eth.balance = result.eth.balance || 0; 52 | 53 | const rates = coinGecko.getRates('ETH'); 54 | const rate = rates.length > 0 ? rates[0] : null; 55 | if (rate) { 56 | result.eth.balanceFiat = parseFloat(rate.current_price) * result.eth.balance; 57 | } 58 | result.tokens = result.tokens.filter(token => blacklistedTokens.indexOf(token.tokenInfo.address) === -1); 59 | result.tokens.forEach((token) => { 60 | token.balance = token.balance / (Math.pow(10, parseInt(token.tokenInfo.decimals))); 61 | 62 | const rates = coinGecko.getRates(token.tokenInfo.symbol); 63 | const rate = rates.length > 0 ? rates[0] : null; 64 | if (rate) { 65 | token.balanceFiat = parseFloat(rate.current_price) * token.balance; 66 | } 67 | }); 68 | 69 | this.stats = result; 70 | } catch(err) { 71 | console.error(`[${this.dashboard.name} :: Ethereum-Balance-API] => ${err.message}`); 72 | } 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/nicehash-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class NicehashBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(NicehashBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { addr: this.dashboard.address }); 20 | } 21 | 22 | async updateStats() { 23 | try { 24 | const balanceData = await util.getUrl(`https://api.nicehash.com/api?method=balance&id=${this.dashboard.user_id}&key=${this.dashboard.api_key}`); 25 | const result = { 26 | balance: parseFloat(balanceData.result['balance_confirmed']), 27 | }; 28 | 29 | const rates = coinGecko.getRates('BTC'); 30 | const rate = rates.length > 0 ? rates[0] : null; 31 | if (rate) { 32 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 33 | } 34 | this.stats = result; 35 | } catch(err) { 36 | console.error(`[${this.dashboard.name} :: Nicehash-Balance-API] => ${err.message}`); 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /api/lib/dashboards/balances/signum-balance.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class SignumBalance extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 5 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(SignumBalance.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { 20 | addressUrl: `https://explorer.signum.network/search/?q=${this.dashboard.address}&submit=Search`, 21 | ticker: 'SIGNA', 22 | coin: 'Signum', 23 | }); 24 | } 25 | 26 | async updateStats() { 27 | try { 28 | const data = await util.getUrl(`https://europe.signum.network/burst?requestType=getAccount&account=${this.dashboard.address}`); 29 | const result = { 30 | balance: data.balanceNQT / 100000000, 31 | }; 32 | 33 | const rates = coinGecko.getRates('SIGNA'); 34 | const rate = rates.length > 0 ? rates[0] : null; 35 | if (rate) { 36 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 37 | } 38 | this.stats = result; 39 | } catch(err) { 40 | console.error(`[${this.dashboard.name} :: SIGNUM-Balance-API] => ${err.message}`); 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /api/lib/dashboards/custom/colab-manager-stats-collection.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const https = require('https') 3 | 4 | const Dashboard = require('../dashboard') 5 | 6 | module.exports = class ColabManagerStatsCollection extends Dashboard { 7 | constructor(options = {}) { 8 | super(options) 9 | 10 | this.apiClient = axios.create({ 11 | baseURL: this.dashboard.baseUrl, 12 | httpsAgent: new https.Agent({ rejectUnauthorized: false }), 13 | }) 14 | this.updateStats() 15 | } 16 | 17 | async updateStats() { 18 | if (!this.apiClient) { 19 | return 20 | } 21 | try { 22 | const { data } = await this.apiClient.get(`/api/stats`) 23 | 24 | const workers = data 25 | .map(manager => manager.workers.map(worker => ({ 26 | managerId: manager.id, 27 | workerId: worker.id, 28 | stats: worker.stats, 29 | }))) 30 | .reduce((acc, curr) => acc.concat(curr), []) 31 | workers.sort((a, b) => { 32 | if (a.stats.documentName < b.stats.documentName) { 33 | return -1 34 | } 35 | if (a.stats.documentName > b.stats.documentName) { 36 | return 1 37 | } 38 | 39 | return 0 40 | }) 41 | this.stats = workers 42 | } catch(err) { 43 | console.error(`[${this.dashboard.name} :: Colab-Manager-Stats-Collection] => ${err.message}`) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/lib/dashboards/custom/dashboard-api.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | const axios = require('axios'); 3 | const util = require('../../util'); 4 | const Dashboard = require('../dashboard'); 5 | const coinGecko = require('../../rates/coingecko'); 6 | 7 | module.exports = class DashboardApi extends Dashboard { 8 | 9 | static getDefaults() { 10 | return { 11 | interval: 5 * 60 * 1000, 12 | }; 13 | } 14 | 15 | constructor(options = {}) { 16 | options = Object.assign(DashboardApi.getDefaults(), options); 17 | super(options); 18 | } 19 | 20 | async updateStats() { 21 | try { 22 | const agent = new https.Agent({ 23 | rejectUnauthorized: false 24 | }); 25 | const minerData = await axios.get(`${this.dashboard.baseUrl}/stats`, {httpsAgent: agent}); 26 | const stats = minerData.data.data; 27 | const total = stats.trainingCount + stats.keepaliveCount + stats.graveyardCount + stats.removedCount; 28 | 29 | const result = { 30 | training: stats.trainingCount, 31 | keepalive: stats.keepaliveCount, 32 | graveyard: stats.graveyardCount, 33 | removed: stats.removedCount, 34 | total, 35 | totalEth: stats.totalEth, 36 | totalStorj: stats.totalStorj, 37 | }; 38 | 39 | let rates = coinGecko.getRates('ETH'); 40 | const ethRate = rates.length > 0 ? rates[0] : null; 41 | rates = coinGecko.getRates('STORJ'); 42 | const storjRate = rates.length > 0 ? rates[0] : null; 43 | if (ethRate) { 44 | result.totalEthFiat = parseFloat(ethRate.current_price) * result.totalEth; 45 | } 46 | if (storjRate) { 47 | result.totalStorjFiat = parseFloat(storjRate.current_price) * result.totalStorj; 48 | } 49 | 50 | this.stats = result; 51 | } catch(err) { 52 | console.error(`[${this.dashboard.name} :: Dashboard-API] => ${err.message}`); 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /api/lib/dashboards/custom/hdpool-control.js: -------------------------------------------------------------------------------- 1 | const IO = require('socket.io-client'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class HDPoolControl extends Dashboard { 6 | onInit() { 7 | this.stats = []; 8 | this.client = IO(`${this.dashboard.baseUrl}/web-ui`); 9 | 10 | this.client.on('connect', this.initStats.bind(this)); 11 | this.client.on('stats/account', this.onNewAccountStats.bind(this)); 12 | } 13 | 14 | initStats() { 15 | this.client.emit('stats/init', this.onStats.bind(this)); 16 | } 17 | 18 | onStats(accounts) { 19 | this.stats = accounts; 20 | } 21 | 22 | onNewAccountStats(accountName, accountStats) { 23 | const stats = this.stats; 24 | if (!stats) { 25 | return; 26 | } 27 | const account = stats.find(account => account.name === accountName); 28 | if (!account) { 29 | return; 30 | } 31 | Object.keys(accountStats).forEach(key => { 32 | account[key] = accountStats[key]; 33 | }); 34 | } 35 | 36 | cleanup() { 37 | this.client.disconnect(); 38 | super.cleanup(); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /api/lib/dashboards/dashboard-util.js: -------------------------------------------------------------------------------- 1 | const Nicehash = require('./pool/nicehash'); 2 | const Miningpoolhub = require('./pool/miningpoolhub'); 3 | const Mpos = require('./pool/mpos'); 4 | const NodeCryptonotePool = require('./pool/node-cryptonote-pool'); 5 | const SnipaNodejsPool = require('./pool/snipa-nodejs-pool'); 6 | const Yiimp = require('./pool/yiimp'); 7 | const HDPool = require('./pool/hdpool'); 8 | const HDPoolControl = require('./custom/hdpool-control'); 9 | const HPool = require('./pool/hpool'); 10 | const DashboardApi = require('./custom/dashboard-api'); 11 | const ColabManagerStatsCollection = require('./custom/colab-manager-stats-collection'); 12 | const BitcoinBalance = require('./balances/bitcoin-balance'); 13 | const CryptoidBalance = require('./balances/cryptoid-balance'); 14 | const CounterpartyBalance = require('./balances/counterparty-balance'); 15 | const EthereumBalance = require('./balances/ethereum-balance'); 16 | const BitmartBalance = require('./balances/bitmart-balance'); 17 | const SignumBalance = require('./balances/signum-balance'); 18 | const NicehashBalance = require('./balances/nicehash-balance'); 19 | const CoinbaseBalance = require('./balances/coinbase-balance'); 20 | const GenericWallet = require('./wallets/generic-wallet'); 21 | const BitbeanWallet = require('./wallets/bitbean-wallet'); 22 | const BHDWallet = require('./wallets/bhd-wallet'); 23 | const DiscWallet = require('./wallets/disc-wallet'); 24 | const BurstWallet = require('./wallets/burst-wallet'); 25 | const BoomWallet = require('./wallets/boom-wallet'); 26 | const WalletAgent = require('./wallets/wallet-agent'); 27 | const FoxyPoolV2 = require('./pool/foxy-pool-v2'); 28 | const ChiaWallet = require('./wallets/chia-wallet'); 29 | const AllTheBlocksBalance = require('./balances/all-the-blocks-balance'); 30 | const BhdBalance = require('./balances/bhd-balance'); 31 | 32 | function getClassForDashboardType(type) { 33 | switch(type) { 34 | case 'foxy-pool-v2': 35 | return FoxyPoolV2; 36 | case 'nicehash': 37 | return Nicehash; 38 | case 'miningpoolhub': 39 | return Miningpoolhub; 40 | case 'genericMPOS': 41 | return Mpos; 42 | case 'hdpool': 43 | return HDPool; 44 | case 'hdpool-control': 45 | return HDPoolControl; 46 | case 'hpool': 47 | return HPool; 48 | case 'dashboard-api': 49 | return DashboardApi; 50 | case 'colab-manager-stats-collection': 51 | return ColabManagerStatsCollection; 52 | case 'bitcoinBalance': 53 | return BitcoinBalance; 54 | case 'cryptoidBalance': 55 | return CryptoidBalance; 56 | case 'counterpartyBalance': 57 | return CounterpartyBalance; 58 | case 'ethBalance': 59 | return EthereumBalance; 60 | case 'bitmart-balance': 61 | return BitmartBalance; 62 | case 'signum-balance': 63 | return SignumBalance; 64 | case 'nicehashBalance': 65 | return NicehashBalance; 66 | case 'node-cryptonote-pool': 67 | return NodeCryptonotePool; 68 | case 'snipa-nodejs-pool': 69 | return SnipaNodejsPool; 70 | case 'yiimp': 71 | return Yiimp; 72 | case 'generic-wallet': 73 | return GenericWallet; 74 | case 'bitbean-wallet': 75 | return BitbeanWallet; 76 | case 'bhd-wallet': 77 | return BHDWallet; 78 | case 'disc-wallet': 79 | return DiscWallet; 80 | case 'burst-wallet': 81 | return BurstWallet; 82 | case 'boom-wallet': 83 | return BoomWallet; 84 | case 'coinbase': 85 | return CoinbaseBalance; 86 | case 'wallet-agent': 87 | return WalletAgent; 88 | case 'chia-wallet': 89 | return ChiaWallet; 90 | case 'all-the-blocks-balance': 91 | return AllTheBlocksBalance; 92 | case 'bhd-balance': 93 | return BhdBalance; 94 | default: 95 | throw new Error(`No class matched '${type}'`); 96 | } 97 | } 98 | 99 | module.exports = { 100 | getClassForDashboardType, 101 | }; 102 | -------------------------------------------------------------------------------- /api/lib/dashboards/dashboard.js: -------------------------------------------------------------------------------- 1 | module.exports = class Dashboard { 2 | 3 | static getDefaults() { 4 | return { 5 | interval: 60 * 1000, 6 | }; 7 | } 8 | 9 | constructor(options = {}) { 10 | this.stats = {}; 11 | options = Object.assign(Dashboard.getDefaults(), options); 12 | this.dashboard = options.dashboard; 13 | this.interval = options.interval; 14 | this.onInit(); 15 | } 16 | 17 | getStats() { 18 | return { 19 | name: this.dashboard.name, 20 | type: this.dashboard.type, 21 | enabled: this.dashboard.enabled, 22 | data: this.stats, 23 | }; 24 | } 25 | 26 | async updateStats() {} 27 | 28 | cleanup() { 29 | if (this.runningInterval) { 30 | clearInterval(this.runningInterval); 31 | this.runningInterval = null; 32 | } 33 | this.stats = {}; 34 | } 35 | 36 | onInit() { 37 | this.updateStats(); 38 | this.runningInterval = setInterval(this.updateStats.bind(this), this.interval); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/foxy-pool-v2.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const moment = require('moment'); 3 | const Dashboard = require('../dashboard'); 4 | const coinGecko = require('../../rates/coingecko'); 5 | 6 | module.exports = class FoxyPoolV2 extends Dashboard { 7 | constructor(options = {}) { 8 | super(options); 9 | this.poolIdentifier = this.dashboard.ticker; 10 | this.stats.coin = this.getCoin(this.poolIdentifier); 11 | } 12 | 13 | getCoin(poolIdentifier) { 14 | switch (poolIdentifier) { 15 | case 'signa': return 'SIGNA'; 16 | case 'bhd': return 'BHD'; 17 | default: return poolIdentifier.toUpperCase(); 18 | } 19 | } 20 | 21 | async onInit() { 22 | super.onInit(); 23 | this.poolIdentifier = this.dashboard.ticker; 24 | 25 | this.client = axios.create({ 26 | baseURL: `https://api.foxypool.io/api/stats`, 27 | }); 28 | await this.initHttpStats(); 29 | setInterval(this.updatePoolStats.bind(this), 61 * 1000); 30 | setInterval(this.updateRoundStats.bind(this), 61 * 1000); 31 | } 32 | 33 | async initHttpStats() { 34 | await Promise.all([ 35 | this.updatePoolStats(), 36 | this.updateRoundStats(), 37 | ]); 38 | } 39 | 40 | async updatePoolStats() { 41 | this.onNewPoolStats(await this.getPoolStats()); 42 | } 43 | 44 | async updateRoundStats() { 45 | this.onNewRoundStats(await this.getRoundStats()); 46 | } 47 | 48 | async getPoolStats() { 49 | const { data } = await this.client.get(`${this.poolIdentifier}/pool`); 50 | 51 | return data; 52 | } 53 | 54 | async getRoundStats() { 55 | const { data } = await this.client.get(`${this.poolIdentifier}/round`); 56 | 57 | return data; 58 | } 59 | 60 | onNewPoolStats(poolStats) { 61 | const miner = poolStats.accounts.find(account => this.dashboard.address === account.payoutAddress); 62 | this.stats.miner = miner ? { 63 | payoutAddress: miner.payoutAddress, 64 | reportedCapacity: miner.reportedCapacity, 65 | pending: parseFloat(miner.pending), 66 | historicalShare: miner.ecShare, 67 | pledge: parseFloat(miner.pledge), 68 | historicalPledgeShare: miner.pledgeShare, 69 | deadlineCount: miner.deadlines, 70 | ec: miner.ec, 71 | online: this.getAccountState(miner), 72 | } : { 73 | payoutAddress: this.dashboard.address, 74 | reportedCapacity: 0, 75 | pending: 0, 76 | historicalShare: 0, 77 | pledge: 0, 78 | historicalPledgeShare: 0, 79 | deadlineCount: 'N/A', 80 | ec: 0, 81 | }; 82 | 83 | const lastPayout = poolStats.payouts.find(payout => payout.transactions.some(transaction => Object.keys(transaction.payoutAmounts).some(currentPayoutAddress => currentPayoutAddress === this.dashboard.address))); 84 | if (!lastPayout) { 85 | this.stats.lastPayout = null; 86 | } else { 87 | const transaction = lastPayout.transactions.find(transaction => Object.keys(transaction.payoutAmounts).some(currentPayoutAddress => currentPayoutAddress === this.dashboard.address)); 88 | this.stats.lastPayout = { 89 | date: moment(lastPayout.createdAt).format('YYYY-MM-DD'), 90 | amount: transaction.payoutAmounts[this.dashboard.address], 91 | }; 92 | } 93 | 94 | const rates = coinGecko.getRates(this.dashboard.ticker); 95 | const rate = rates.length > 0 ? rates[0] : null; 96 | if (!rate) { 97 | return; 98 | } 99 | this.stats.miner.pendingFiat = parseFloat(rate.current_price) * this.stats.miner.pending; 100 | this.stats.miner.pledgeFiat = parseFloat(rate.current_price) * this.stats.miner.pledge; 101 | if (this.stats.lastPayout) { 102 | this.stats.lastPayout.amountFiat = parseFloat(rate.current_price) * parseFloat(this.stats.lastPayout.amount); 103 | } 104 | } 105 | 106 | onNewRoundStats(roundStats) { 107 | this.roundStats = roundStats; 108 | } 109 | 110 | getAccountState(account) { 111 | const lastSubmitHeight = account.lastSubmissionHeight; 112 | if (!lastSubmitHeight) { 113 | return account.pledgeShare > 0 ? 3 : 0; 114 | } 115 | if (!this.roundStats || !this.roundStats.round) { 116 | return 1; 117 | } 118 | if (this.roundStats.round.height - lastSubmitHeight > 6) { 119 | return 0; 120 | } 121 | if (this.roundStats.round.height - lastSubmitHeight > 3) { 122 | return 2; 123 | } 124 | 125 | return 1; 126 | } 127 | }; 128 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/hpool.js: -------------------------------------------------------------------------------- 1 | const HpoolApi = require('hpool-api'); 2 | const moment = require('moment'); 3 | const bytes = require('bytes'); 4 | const Dashboard = require('../dashboard'); 5 | const coinGecko = require('../../rates/coingecko'); 6 | 7 | module.exports = class HPool extends Dashboard { 8 | 9 | static getDefaults() { 10 | return { 11 | interval: 3 * 60 * 1000, 12 | }; 13 | } 14 | 15 | constructor(options = {}) { 16 | options = Object.assign(HPool.getDefaults(), options); 17 | super(options); 18 | } 19 | 20 | getStats() { 21 | return Object.assign(super.getStats(), { 22 | symbol: 'BHD', 23 | }); 24 | } 25 | 26 | async onInit() { 27 | this.client = new HpoolApi(this.dashboard.api_key, this.dashboard.user_id, this.dashboard.address); 28 | 29 | super.onInit(); 30 | } 31 | 32 | async updateStats() { 33 | const pendingBalance = await this.client.getPendingBalance(); 34 | const mortgageInfo = await this.client.getMortgageInfo(); 35 | const miners = await this.client.getMiner(); 36 | const earningsHistory = await this.client.getEarningsHistory(); 37 | 38 | this.stats.lastPayedTs = earningsHistory.length > 0 ? earningsHistory[0].created : moment().toISOString(); 39 | this.stats.unconfirmed = pendingBalance; 40 | this.stats.balance = mortgageInfo.total_amount; 41 | this.stats.incomeLastDay = earningsHistory.length > 0 ? earningsHistory[0].amount : 0; 42 | this.stats.miners = miners.map(miner => ({ 43 | name: miner.miner_name, 44 | capacityString: bytes(bytes(`${miner.capacity}GB`)), 45 | capacity: bytes(`${miner.capacity}GB`), 46 | online: moment().diff(moment(miner.updated), 'minutes') < 5, 47 | lastSeen: moment(miner.updated).toDate(), 48 | })).sort((a, b) => { 49 | if (a.name < b.name) { 50 | return -1; 51 | } 52 | if (a.name > b.name) { 53 | return 1; 54 | } 55 | return 0; 56 | }); 57 | this.stats.onlineCapacity = this.stats.miners.reduce((capacity, miner) => capacity + miner.capacity, 0); 58 | this.stats.onlineCapacityString = bytes(this.stats.onlineCapacity); 59 | 60 | const rate = coinGecko.getRates('BHD').find(rate => rate.id === 'bitcoin-hd'); 61 | if (!rate) { 62 | return; 63 | } 64 | this.stats.balanceFiat = parseFloat(rate.current_price) * this.stats.balance; 65 | this.stats.incomeLastDayFiat = parseFloat(rate.current_price) * this.stats.incomeLastDay; 66 | this.stats.unconfirmedFiat = parseFloat(rate.current_price) * this.stats.unconfirmed; 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/miningpoolhub.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Mpos = require('./mpos'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class Miningpoolhub extends Mpos { 6 | 7 | async updateStats() { 8 | try { 9 | const poolData = await util.getUrl(`https://miningpoolhub.com/index.php?page=api&action=getminingandprofitsstatistics`); 10 | const coinArr = poolData.return; 11 | const statsData = []; 12 | let profitabilitySum = 0; 13 | let balanceSumFiat = 0; 14 | let balanceAESumFiat = 0; 15 | let balanceOnExchangeSumFiat = 0; 16 | let balanceTotalSumFiat = 0; 17 | for (let coin of coinArr) { 18 | let dashboardData = await util.getUrl(`https://${coin.coin_name}.miningpoolhub.com/index.php?page=api&action=getdashboarddata&api_key=${this.dashboard.api_key}&id=${this.dashboard.user_id}`); 19 | dashboardData = dashboardData.getdashboarddata.data; 20 | const coinStats = { 21 | name: coin.coin_name.charAt(0).toUpperCase() + coin.coin_name.slice(1), 22 | profitability: coin.profit / 1000000.0, //make gh/s->kh/s for easier calculation 23 | balance: dashboardData.balance, 24 | balance_ae: dashboardData.balance_for_auto_exchange, 25 | onExchange: dashboardData.balance_on_exchange, 26 | hashrate: dashboardData.raw.personal.hashrate, //kh/s 27 | symbol: dashboardData.pool.info.currency 28 | }; 29 | try { 30 | const workerData = await util.getUrl(`https://${coin.coin_name}.miningpoolhub.com/index.php?page=api&action=getuserworkers&api_key=${this.dashboard.api_key}&id=${this.dashboard.user_id}`); 31 | if (Array.isArray(workerData.getuserworkers.data)) { 32 | coinStats.workers = workerData.getuserworkers.data 33 | .filter((worker) => worker.hashrate !== 0) 34 | .map((worker) => { 35 | const arr = worker.username.split('.'); 36 | worker.username = arr[(arr.length === 1 ? 0 : 1)]; 37 | return worker; 38 | }); 39 | } 40 | } catch (err) {} // discard worker retrieval errors 41 | const rates = coinGecko.getRates(coinStats.symbol); 42 | const rate = rates.length > 0 ? rates[0] : null; 43 | if (rate) { 44 | coinStats.balance.confirmedFiat = parseFloat(rate.current_price) * coinStats.balance.confirmed; 45 | coinStats.balance.unconfirmedFiat = parseFloat(rate.current_price) * coinStats.balance.unconfirmed; 46 | coinStats.balance_ae.confirmedFiat = parseFloat(rate.current_price) * coinStats.balance_ae.confirmed; 47 | coinStats.balance_ae.unconfirmedFiat = parseFloat(rate.current_price) * coinStats.balance_ae.unconfirmed; 48 | coinStats.onExchangeFiat = parseFloat(rate.current_price) * coinStats.onExchange; 49 | const balanceFiat = coinStats.balance.confirmedFiat + coinStats.balance.unconfirmedFiat; 50 | const balanceAEFiat = coinStats.balance_ae.confirmedFiat + coinStats.balance_ae.unconfirmedFiat; 51 | balanceSumFiat += balanceFiat; 52 | balanceAESumFiat += balanceAEFiat; 53 | balanceOnExchangeSumFiat += coinStats.onExchangeFiat; 54 | balanceTotalSumFiat += balanceFiat + balanceAEFiat + coinStats.onExchangeFiat; 55 | } 56 | statsData.push(coinStats); 57 | profitabilitySum += coinStats.profitability * coinStats.hashrate; 58 | } 59 | const result = { 60 | statsData, 61 | profitabilitySum, 62 | balanceSumFiat, 63 | balanceAESumFiat, 64 | balanceOnExchangeSumFiat, 65 | balanceTotalSumFiat, 66 | }; 67 | const rates = coinGecko.getRates('BTC'); 68 | const rate = rates.length > 0 ? rates[0] : null; 69 | if (rate) { 70 | result.profitabilitySumFiat = parseFloat(rate.current_price) * result.profitabilitySum; 71 | } 72 | 73 | this.stats = result; 74 | } catch(err) { 75 | console.error(`[${this.dashboard.name} :: Miningpoolhub-API] => ${err.message}`); 76 | } 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/mpos.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class Mpos extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 3 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(Mpos.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { baseUrl: this.dashboard.baseUrl }); 20 | } 21 | 22 | async updateStats() { 23 | try { 24 | let dashboardData = await util.getUrl(`${this.dashboard.baseUrl}/index.php?page=api&action=getdashboarddata&api_key=${this.dashboard.api_key}&id=${this.dashboard.user_id}`); 25 | dashboardData = dashboardData.getdashboarddata.data; 26 | let workerData = await util.getUrl(`${this.dashboard.baseUrl}/index.php?page=api&action=getuserworkers&api_key=${this.dashboard.api_key}&id=${this.dashboard.user_id}`); 27 | workerData = workerData.getuserworkers.data; 28 | let balanceData = await util.getUrl(`${this.dashboard.baseUrl}/index.php?page=api&action=getuserbalance&api_key=${this.dashboard.api_key}&id=${this.dashboard.user_id}`); 29 | balanceData = balanceData.getuserbalance.data; 30 | workerData = workerData 31 | .sort((a, b) => { 32 | if (a.username < b.username) return -1; 33 | if (a.username > b.username) return 1; 34 | return 0; 35 | }) 36 | .filter((worker) => worker.hashrate !== 0) 37 | .map((worker) => { 38 | const arr = worker.username.split('.'); 39 | worker.username = arr[(arr.length === 1 ? 0 : 1)]; 40 | worker.hashrate = worker.hashrate / this.dashboard.hrModifier; 41 | return worker; 42 | }); 43 | 44 | const result = { 45 | hashrate: dashboardData.raw.personal.hashrate / this.dashboard.hrModifier, 46 | symbol: dashboardData.pool.info.currency, 47 | estimated: dashboardData.personal.estimates.payout, 48 | workers: workerData, 49 | confirmed: parseFloat(balanceData.confirmed), 50 | unconfirmed: parseFloat(balanceData.unconfirmed), 51 | }; 52 | 53 | const rates = coinGecko.getRates(result.symbol); 54 | const rate = rates.length > 0 ? rates[0] : null; 55 | if (rate) { 56 | result.confirmedFiat = parseFloat(rate.current_price) * result.confirmed; 57 | result.unconfirmedFiat = parseFloat(rate.current_price) * result.unconfirmed; 58 | result.estimatedFiat = parseFloat(rate.current_price) * result.estimated; 59 | } 60 | 61 | this.stats = result; 62 | } catch(err) { 63 | console.error(`[${this.dashboard.name} :: MPOS-API] => ${err.message}`); 64 | } 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/nicehash.js: -------------------------------------------------------------------------------- 1 | const Dashboard = require('../dashboard'); 2 | const coinGecko = require('../../rates/coingecko'); 3 | const NicehashApi = require('../../nicehash-api'); 4 | 5 | module.exports = class Nicehash extends Dashboard { 6 | static getDefaults() { 7 | return { 8 | interval: 3 * 60 * 1000, 9 | }; 10 | } 11 | 12 | constructor(options = {}) { 13 | options = Object.assign(Nicehash.getDefaults(), options); 14 | super(options); 15 | } 16 | 17 | onInit() { 18 | this.nicehashApi = new NicehashApi({ 19 | apiKey: this.dashboard.api_key, 20 | apiSecret: this.dashboard.address, 21 | organizationId: this.dashboard.user_id, 22 | }); 23 | super.onInit(); 24 | } 25 | 26 | async updateStats() { 27 | const miningStats = await this.nicehashApi.getMiningStats(); 28 | const btcRates = coinGecko.getRates('BTC'); 29 | const btcRate = btcRates.length > 0 ? btcRates[0] : null; 30 | if (btcRate) { 31 | miningStats.totalProfitabilityFiat = parseFloat(btcRate.current_price) * miningStats.totalProfitability; 32 | miningStats.unpaidAmountFiat = parseFloat(btcRate.current_price) * parseFloat(miningStats.unpaidAmount); 33 | } 34 | 35 | const accountDetails = await this.nicehashApi.getAccount(); 36 | const accountTotalCurrencyRates = coinGecko.getRates(accountDetails.total.currency); 37 | const accountTotalCurrencyRate = accountTotalCurrencyRates.length > 0 ? accountTotalCurrencyRates[0] : null; 38 | if (accountTotalCurrencyRate) { 39 | accountDetails.total.availableFiat = parseFloat(accountTotalCurrencyRate.current_price) * parseFloat(accountDetails.total.available); 40 | accountDetails.total.pendingFiat = parseFloat(accountTotalCurrencyRate.current_price) * parseFloat(accountDetails.total.pending); 41 | accountDetails.total.totalBalanceFiat = parseFloat(accountTotalCurrencyRate.current_price) * parseFloat(accountDetails.total.totalBalance); 42 | } 43 | const positiveBalances = accountDetails.currencies.filter(currency => parseFloat(currency.totalBalance) > 0); 44 | positiveBalances.forEach(balance => { 45 | const rates = coinGecko.getRates(balance.currency); 46 | const rate = rates.length > 0 ? rates[0] : null; 47 | if (rate) { 48 | balance.availableFiat = parseFloat(rate.current_price) * parseFloat(balance.available); 49 | balance.pendingFiat = parseFloat(rate.current_price) * parseFloat(balance.pending); 50 | balance.totalBalanceFiat = parseFloat(rate.current_price) * parseFloat(balance.totalBalance); 51 | } 52 | }); 53 | 54 | this.stats = { 55 | miningStats, 56 | totalBalance: accountDetails.total, 57 | positiveBalances, 58 | }; 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/node-cryptonote-pool.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const util = require('../../util'); 3 | const Dashboard = require('../dashboard'); 4 | const coinGecko = require('../../rates/coingecko'); 5 | 6 | module.exports = class NodeCryptonotePool extends Dashboard { 7 | 8 | static getDefaults() { 9 | return { 10 | interval: 3 * 60 * 1000, 11 | }; 12 | } 13 | 14 | constructor(options = {}) { 15 | options = Object.assign(NodeCryptonotePool.getDefaults(), options); 16 | super(options); 17 | } 18 | 19 | getStats() { 20 | return Object.assign(super.getStats(), { baseUrl: this.dashboard.baseUrl }); 21 | } 22 | 23 | async updateStats() { 24 | try { 25 | const dashboardData = await util.getUrl(`${this.dashboard.baseUrl}/stats_address?address=${this.dashboard.address}&longpoll=false`); 26 | const liveStats = await util.getUrl(`${this.dashboard.baseUrl}/stats`); 27 | 28 | if (dashboardData.error) { 29 | this.stats = { 30 | hashrate: 0, 31 | pending: 0, 32 | paid: 0, 33 | lastShareSubmitted: 'never', 34 | estimatedProfit: 0, 35 | lastBlockFound: moment(parseInt(liveStats.pool.lastBlockFound, 10)).fromNow(), 36 | pendingFiat: 0, 37 | paidFiat: 0, 38 | estimatedProfitFiat: 0, 39 | symbol: liveStats.config.symbol.toUpperCase(), 40 | }; 41 | return; 42 | } 43 | 44 | const hashrate = util.parseHashrate(dashboardData.stats.hashrate || '0 H'); 45 | 46 | const reward = liveStats.network.reward / liveStats.config.coinUnits; 47 | const daysToFindBlock = (liveStats.network.difficulty / hashrate) / (60 * 60 * 24); 48 | const estimatedDailyProfit = reward / daysToFindBlock; 49 | 50 | const result = { 51 | hashrate, 52 | symbol: liveStats.config.symbol.toUpperCase(), 53 | pending: dashboardData.stats.balance ? dashboardData.stats.balance / liveStats.config.coinUnits : 0, 54 | paid: dashboardData.stats.paid ? dashboardData.stats.paid / liveStats.config.coinUnits : 0, 55 | lastShareSubmitted: moment.unix(dashboardData.stats.lastShare).fromNow(), 56 | estimatedProfit: estimatedDailyProfit, 57 | lastBlockFound: moment(parseInt(liveStats.pool.lastBlockFound, 10)).fromNow(), 58 | }; 59 | 60 | const rates = coinGecko.getRates(result.symbol); 61 | const rate = rates.length > 0 ? rates[0] : null; 62 | if (rate) { 63 | result.pendingFiat = parseFloat(rate.current_price) * result.pending; 64 | result.paidFiat = parseFloat(rate.current_price) * result.paid; 65 | result.estimatedProfitFiat = parseFloat(rate.current_price) * result.estimatedProfit; 66 | } 67 | 68 | this.stats = result; 69 | } catch(err) { 70 | console.error(`[${this.dashboard.name} :: Node-Cryptonote-Pool-API] => ${err.message}`); 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/snipa-nodejs-pool.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const util = require('../../util'); 3 | const Dashboard = require('../dashboard'); 4 | const coinGecko = require('../../rates/coingecko'); 5 | 6 | module.exports = class SnipaNodejsPool extends Dashboard { 7 | 8 | static getDefaults() { 9 | return { 10 | interval: 3 * 60 * 1000, 11 | }; 12 | } 13 | 14 | constructor(options = {}) { 15 | options = Object.assign(SnipaNodejsPool.getDefaults(), options); 16 | super(options); 17 | } 18 | 19 | getStats() { 20 | return Object.assign(super.getStats(), { baseUrl: this.dashboard.baseUrl }); 21 | } 22 | 23 | async updateStats() { 24 | try { 25 | const dashboardData = await util.getUrl(`${this.dashboard.baseUrl}/api/miner/${this.dashboard.address}/stats`); 26 | const networkStats = await util.getUrl(`${this.dashboard.baseUrl}/api/network/stats`); 27 | const poolStats = await util.getUrl(`${this.dashboard.baseUrl}/api/pool/stats`); 28 | 29 | 30 | if (dashboardData.lastHash === null) { 31 | this.stats = { 32 | hashrate: 0, 33 | pending: 0, 34 | paid: 0, 35 | lastShareSubmitted: 'never', 36 | estimatedProfit: 0, 37 | lastBlockFound: moment.unix(poolStats.pool_statistics.lastBlockFoundTime).fromNow(), 38 | pendingFiat: 0, 39 | paidFiat: 0, 40 | estimatedProfitFiat: 0, 41 | symbol: this.dashboard.ticker.toUpperCase(), 42 | }; 43 | return; 44 | } 45 | 46 | const reward = networkStats.value / Math.pow(10, 8); 47 | const daysToFindBlock = (networkStats.difficulty / (dashboardData.hash || 0)) / (60 * 60 * 24); 48 | const estimatedDailyProfit = reward / daysToFindBlock; 49 | 50 | const result = { 51 | hashrate: dashboardData.hash || 0, 52 | symbol: this.dashboard.ticker.toUpperCase(), 53 | pending: (dashboardData.amtDue ? (dashboardData.amtDue / Math.pow(10, 8)) : 0) * (this.dashboard.hrModifier || 1), 54 | paid: (dashboardData.amtPaid ? (dashboardData.amtPaid / Math.pow(10, 8)) : 0) * (this.dashboard.hrModifier || 1), 55 | lastShareSubmitted: moment.unix(dashboardData.lastHash).fromNow(), 56 | estimatedProfit: estimatedDailyProfit * (this.dashboard.hrModifier || 1), 57 | lastBlockFound: moment.unix(poolStats.pool_statistics.lastBlockFoundTime).fromNow(), 58 | }; 59 | 60 | const rates = coinGecko.getRates(result.symbol); 61 | const rate = rates.length > 0 ? rates[0] : null; 62 | if (rate) { 63 | result.pendingFiat = parseFloat(rate.current_price) * result.pending; 64 | result.paidFiat = parseFloat(rate.current_price) * result.paid; 65 | result.estimatedProfitFiat = parseFloat(rate.current_price) * result.estimatedProfit; 66 | } 67 | 68 | this.stats = result; 69 | } catch(err) { 70 | console.error(`[${this.dashboard.name} :: Snipa-Nodejs-Pool-API] => ${err.message}`); 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /api/lib/dashboards/pool/yiimp.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class Yiimp extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 3 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(Yiimp.getDefaults(), options); 15 | super(options); 16 | } 17 | 18 | getStats() { 19 | return Object.assign(super.getStats(), { 20 | baseUrl: this.dashboard.baseUrl, 21 | address: this.dashboard.address, 22 | }); 23 | } 24 | 25 | async updateStats() { 26 | try { 27 | let statsData = await util.getUrl(`${this.dashboard.baseUrl}/api/walletEx?address=${this.dashboard.address}`); 28 | 29 | const result = { 30 | hashrate: null, 31 | symbol: statsData.currency, 32 | workers: statsData.miners || [], 33 | balance: statsData.balance, 34 | unconfirmed: statsData.unsold, 35 | paid24h: statsData.paid24h, 36 | }; 37 | result.hashrate = result.workers.reduce((acc, right) => acc + right.accepted, 0); 38 | 39 | const rates = coinGecko.getRates(result.symbol); 40 | const rate = rates.length > 0 ? rates[0] : null; 41 | if (rate) { 42 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 43 | result.unconfirmedFiat = parseFloat(rate.current_price) * result.unconfirmed; 44 | result.paid24hFiat = parseFloat(rate.current_price) * result.paid24h; 45 | } 46 | 47 | this.stats = result; 48 | } catch(err) { 49 | console.error(`[${this.dashboard.name} :: Yiimp-API] => ${err.message}`); 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /api/lib/dashboards/wallets/bhd-wallet.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const GenericWallet = require('./generic-wallet'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class BHDWallet extends GenericWallet { 6 | 7 | constructor(options = {}) { 8 | options.dashboard.ticker = 'BHD'; 9 | super(options); 10 | } 11 | 12 | async getDataForNewWallet() { 13 | const result = await super.getDataForNewWallet(); 14 | const { result: pledgeData } = await util.postUrl(this.dashboard.baseUrl, { 15 | jsonrpc: '2.0', 16 | id: 0, 17 | method: 'getpledge', 18 | params: [], 19 | }); 20 | result.pledgeAmount = pledgeData.pledge || pledgeData.miningRequireBalance; 21 | result.pledge = pledgeData.borrowBalance; 22 | result.pledgeCapacity = pledgeData.capacity; 23 | 24 | return result; 25 | } 26 | 27 | async updateStats() { 28 | const getDataForWallet = this.getDataForNewWallet.bind(this); 29 | try { 30 | const result = await getDataForWallet(); 31 | 32 | const rates = coinGecko.getRates(this.dashboard.ticker); 33 | const rate = rates.find(rate => rate.id === 'bitcoin-hd'); 34 | if (rate) { 35 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 36 | result.totalFiat = parseFloat(rate.current_price) * result.total; 37 | if (result.unconfirmed !== undefined) { 38 | result.unconfirmedFiat = parseFloat(rate.current_price) * result.unconfirmed; 39 | } 40 | } 41 | this.stats = result; 42 | } catch(err) { 43 | console.error(`[${this.dashboard.name} :: BHD-Wallet-API] => ${err.message}`); 44 | this.stats = null; 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /api/lib/dashboards/wallets/bitbean-wallet.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const util = require('../../util'); 3 | const GenericWallet = require('./generic-wallet'); 4 | const coinGecko = require('../../rates/coingecko'); 5 | 6 | module.exports = class BitbeanWallet extends GenericWallet { 7 | 8 | constructor(options = {}) { 9 | options.dashboard.ticker = 'BITB'; 10 | super(options); 11 | } 12 | 13 | async getDataForOldWallet() { 14 | const result = await super.getDataForOldWallet(); 15 | const sproutingData = await util.postUrl(this.dashboard.baseUrl, { 16 | jsonrpc: '1.0', 17 | id: 0, 18 | method: 'getsproutinginfo', 19 | params: [], 20 | }); 21 | result.sprouting = sproutingData.result.Enabled && sproutingData.result.Sprouting; 22 | if (result.sprouting) { 23 | result.sproutingInterval = moment.duration(sproutingData.result['Expected Time'], 'seconds').humanize(); 24 | } 25 | 26 | return result; 27 | } 28 | 29 | async updateStats() { 30 | try { 31 | const result = await this.getDataForOldWallet(); 32 | const rates = coinGecko.getRates(this.dashboard.ticker); 33 | const rate = rates.length > 0 ? rates[0] : null; 34 | if (rate) { 35 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 36 | result.totalFiat = parseFloat(rate.current_price) * result.total; 37 | result.unconfirmedFiat = parseFloat(rate.current_price) * result.unconfirmed; 38 | result.stakedFiat = parseFloat(rate.current_price) * result.staked; 39 | } 40 | this.stats = result; 41 | } catch(err) { 42 | console.error(`[${this.dashboard.name} :: Bitbean-Wallet-API] => ${err.message}`); 43 | this.stats = null; 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /api/lib/dashboards/wallets/boom-wallet.js: -------------------------------------------------------------------------------- 1 | const superagent = require('superagent'); 2 | const JSONbig = require('json-bigint'); 3 | const BurstWallet = require('./burst-wallet'); 4 | const coinGecko = require('../../rates/coingecko'); 5 | const Capacity = require('../../capacity'); 6 | 7 | module.exports = class BoomWallet extends BurstWallet { 8 | constructor(options = {}) { 9 | options = Object.assign(BurstWallet.getDefaults(), options); 10 | options.endpoint = 'boom'; 11 | options.dashboard.ticker = options.dashboard.ticker || 'BOOM'; 12 | super(options); 13 | } 14 | 15 | async updateStats() { 16 | try { 17 | const balance = await this.getBalance(); 18 | const peerCount = await this.getPeerCount(); 19 | const lastBlockReceived = await this.getLastBlockReceived(); 20 | const pledge = await this.getPledge(); 21 | const pledgeCapacity = await this.pledgeCapacity(); 22 | const pledgeAmount = pledgeCapacity * 30; 23 | 24 | const result = { 25 | balance, 26 | unconfirmed: 0, 27 | total: balance, 28 | connections: peerCount, 29 | lastBlockReceived, 30 | pledgeCapacity: Capacity.fromTiB(pledgeCapacity).toString(), 31 | pledgeAmount, 32 | pledge, 33 | syncProgress: 1, 34 | }; 35 | 36 | const rates = coinGecko.getRates('BOOM'); 37 | const rate = rates.find(rate => rate.id === 'boom-coin'); 38 | if (rate) { 39 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 40 | result.totalFiat = parseFloat(rate.current_price) * result.total; 41 | } 42 | this.stats = result; 43 | } catch(err) { 44 | console.error(`[${this.dashboard.name} :: BOOM-Wallet-API] => ${err.message}`); 45 | } 46 | } 47 | 48 | async getPledge() { 49 | const accountInfo = await this.doApiCall('getAccount', { 50 | account: this.dashboard.address, 51 | }); 52 | 53 | return parseInt(accountInfo.pledgesIn, 10) / Math.pow(10, 8); 54 | } 55 | 56 | async pledgeCapacity() { 57 | const res = await this.doApiCall('getCapacity', { 58 | account: this.dashboard.address, 59 | }); 60 | 61 | return parseFloat(res.capacity); 62 | } 63 | 64 | async doApiCall(requestType, params = {}, method = 'get') { 65 | const queryParams = Object.assign(params, {requestType}); 66 | const res = await superagent[method](`${this.dashboard.baseUrl}/boom`).query(queryParams); 67 | 68 | return JSONbig.parse(res.text); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /api/lib/dashboards/wallets/burst-wallet.js: -------------------------------------------------------------------------------- 1 | const superagent = require('superagent'); 2 | const JSONbig = require('json-bigint'); 3 | const Dashboard = require('../dashboard'); 4 | const coinGecko = require('../../rates/coingecko'); 5 | 6 | module.exports = class BurstWallet extends Dashboard { 7 | 8 | static getDefaults() { 9 | return { 10 | interval: 2 * 60 * 1000, 11 | }; 12 | } 13 | 14 | constructor(options = {}) { 15 | options = Object.assign(BurstWallet.getDefaults(), options); 16 | options.dashboard.ticker = options.dashboard.ticker || 'BURST'; 17 | super(options); 18 | } 19 | 20 | getStats() { 21 | return Object.assign(super.getStats(), { 22 | ticker: this.dashboard.ticker.toUpperCase(), 23 | }); 24 | } 25 | 26 | async updateStats() { 27 | try { 28 | const balance = await this.getBalance(); 29 | const peerCount = await this.getPeerCount(); 30 | const lastBlockReceived = await this.getLastBlockReceived(); 31 | 32 | const result = { 33 | balance, 34 | unconfirmed: 0, 35 | total: balance, 36 | connections: peerCount, 37 | lastBlockReceived, 38 | syncProgress: 1, 39 | }; 40 | 41 | const rates = coinGecko.getRates('BURST'); 42 | const rate = rates.length > 0 ? rates[0] : null; 43 | if (rate) { 44 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 45 | result.totalFiat = parseFloat(rate.current_price) * result.total; 46 | } 47 | this.stats = result; 48 | } catch(err) { 49 | console.error(`[${this.dashboard.name} :: BURST-Wallet-API] => ${err.message}`); 50 | } 51 | } 52 | 53 | async getLastBlockReceived() { 54 | const block = await this.doApiCall('getBlock'); 55 | 56 | return block.timestamp * 1000; 57 | } 58 | 59 | async getBalance() { 60 | const accountInfo = await this.doApiCall('getAccount', { 61 | account: this.dashboard.address, 62 | }); 63 | 64 | return parseInt(accountInfo.balanceNQT, 10) / Math.pow(10, 8); 65 | } 66 | 67 | async getPeerCount() { 68 | const peers = await this.doApiCall('getPeers', { 69 | active: true, 70 | }); 71 | 72 | return peers.peers.length; 73 | } 74 | 75 | async doApiCall(requestType, params = {}, method = 'get') { 76 | const queryParams = Object.assign(params, {requestType}); 77 | const res = await superagent[method](`${this.dashboard.baseUrl}/burst`).query(queryParams); 78 | 79 | return JSONbig.parse(res.text); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /api/lib/dashboards/wallets/chia-wallet.js: -------------------------------------------------------------------------------- 1 | const { Agent: HttpsAgent } = require('https'); 2 | const axios = require('axios'); 3 | 4 | const Dashboard = require('../dashboard'); 5 | const coinGecko = require('../../rates/coingecko'); 6 | 7 | module.exports = class ChiaWallet extends Dashboard { 8 | static getDefaults() { 9 | return { 10 | interval: 60 * 1000, 11 | }; 12 | } 13 | 14 | constructor(options = {}) { 15 | options = Object.assign(ChiaWallet.getDefaults(), options); 16 | super(options); 17 | } 18 | 19 | onInit() { 20 | this.client = axios.create({ 21 | baseURL: this.dashboard.baseUrl, 22 | httpsAgent: new HttpsAgent({ 23 | rejectUnauthorized: false, 24 | cert: this.dashboard.api_key.split(':')[0].split('\\n').join('\n'), 25 | key: this.dashboard.api_key.split(':')[1].split('\\n').join('\n'), 26 | }), 27 | }); 28 | this.client.interceptors.response.use((response) => { 29 | if (response.data.error) { 30 | throw new Error(response.data.error); 31 | } 32 | 33 | return response; 34 | }); 35 | this.updateStats(); 36 | this.runningInterval = setInterval(this.updateStats.bind(this), this.interval); 37 | } 38 | 39 | async retrieveStats() { 40 | const { data: syncData } = await this.client.post('/get_sync_status', {}); 41 | const result = { 42 | syncProgress: syncData.syncing ? 0 : 1, 43 | lastBlockReceived: Date.now() / 1e3, // placeholder because unsupported 44 | }; 45 | const { data: balanceData } = await this.client.post('/get_wallet_balance', { wallet_id: 1 }); 46 | result.balance = balanceData.wallet_balance.spendable_balance / 1e12; 47 | result.unconfirmed = (balanceData.wallet_balance.unconfirmed_wallet_balance / 1e12) - result.balance; 48 | result.total = result.balance + result.unconfirmed; 49 | 50 | return result; 51 | } 52 | 53 | async updateStats() { 54 | try { 55 | const result = await this.retrieveStats(); 56 | 57 | const rates = coinGecko.getRates('xch'); 58 | const rate = rates.length > 0 ? rates[0] : null; 59 | if (rate) { 60 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 61 | result.totalFiat = parseFloat(rate.current_price) * result.total; 62 | if (result.unconfirmed !== undefined) { 63 | result.unconfirmedFiat = parseFloat(rate.current_price) * result.unconfirmed; 64 | } 65 | } 66 | this.stats = result; 67 | } catch(err) { 68 | console.error(`[${this.dashboard.name} :: Chia-Wallet-API] => ${err.message}`); 69 | this.stats = null; 70 | } 71 | } 72 | 73 | getStats() { 74 | return Object.assign(super.getStats(), { 75 | ticker: 'XCH', 76 | }); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /api/lib/dashboards/wallets/disc-wallet.js: -------------------------------------------------------------------------------- 1 | const GenericWallet = require('./generic-wallet'); 2 | const coinGecko = require('../../rates/coingecko'); 3 | 4 | module.exports = class DiscWallet extends GenericWallet { 5 | 6 | constructor(options = {}) { 7 | options.dashboard.ticker = 'DISC'; 8 | super(options); 9 | } 10 | 11 | async getDataForNewWallet() { 12 | const result = await super.getDataForNewWallet(); 13 | result.syncProgress = 1; 14 | 15 | return result; 16 | } 17 | 18 | async updateStats() { 19 | const getDataForWallet = this.getDataForNewWallet.bind(this); 20 | try { 21 | const result = await getDataForWallet(); 22 | 23 | const rates = coinGecko.getRates(this.dashboard.ticker); 24 | const rate = rates.length > 0 ? rates[0] : null; 25 | if (rate) { 26 | result.balanceFiat = parseFloat(rate.current_price) * result.balance; 27 | result.totalFiat = parseFloat(rate.current_price) * result.total; 28 | if (result.unconfirmed !== undefined) { 29 | result.unconfirmedFiat = parseFloat(rate.current_price) * result.unconfirmed; 30 | } 31 | } 32 | this.stats = result; 33 | } catch(err) { 34 | console.error(`[${this.dashboard.name} :: Disc-Wallet-API] => ${err.message}`); 35 | this.stats = null; 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /api/lib/dashboards/wallets/wallet-agent.js: -------------------------------------------------------------------------------- 1 | const util = require('../../util'); 2 | const Dashboard = require('../dashboard'); 3 | const coinGecko = require('../../rates/coingecko'); 4 | 5 | module.exports = class WalletAgent extends Dashboard { 6 | 7 | static getDefaults() { 8 | return { 9 | interval: 2 * 60 * 1000, 10 | }; 11 | } 12 | 13 | constructor(options = {}) { 14 | options = Object.assign(WalletAgent.getDefaults(), options); 15 | super(options); 16 | this.stats = null; 17 | } 18 | 19 | async updateStats() { 20 | try { 21 | this.stats = await util.getUrl(`${this.dashboard.baseUrl}/stats`); 22 | } catch(err) { 23 | console.error(`[${this.dashboard.name} :: Wallet-Agent-API] => ${err.message}`); 24 | } 25 | if (!this.stats) { 26 | return; 27 | } 28 | this.stats.map(wallet => { 29 | if (!wallet.data || Object.keys(wallet.data).length === 0) { 30 | return; 31 | } 32 | 33 | const rates = coinGecko.getRates(wallet.ticker); 34 | let rate = rates.length > 0 ? rates[0] : null; 35 | if (wallet.ticker.toUpperCase() === 'BHD') { // BHD is last 36 | rate = rates.find(rate => rate.id === 'bitcoin-hd'); 37 | } 38 | 39 | if (rate) { 40 | wallet.data.balanceFiat = parseFloat(rate.current_price) * (wallet.data.balance || 0); 41 | wallet.data.unconfirmedFiat = parseFloat(rate.current_price) * (wallet.data.unconfirmed || 0); 42 | wallet.data.totalFiat = parseFloat(rate.current_price) * (wallet.data.total || 0); 43 | } 44 | }); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /api/lib/miner/burst-proxy.js: -------------------------------------------------------------------------------- 1 | const IO = require('socket.io-client'); 2 | const Miner = require('./miner'); 3 | 4 | module.exports = class BurstProxy extends Miner { 5 | static getBurstBlockReward(blockHeight) { 6 | const month = Math.floor(blockHeight / 10800); 7 | return 10000 * Math.pow(95, month) / Math.pow(100, month); 8 | } 9 | 10 | static getBHDBlockReward() { 11 | return 23.75; // simplified 12 | } 13 | 14 | getStats() { 15 | return Object.assign(super.getStats(), {stats: this.stats}); 16 | } 17 | 18 | onInit() { 19 | this.stats = []; 20 | this.client = IO(`${this.device.hostname}/web-ui`); 21 | 22 | this.client.on('stats/proxy', this.onNewProxyStats.bind(this)); 23 | this.client.on('stats/current-round', this.onNewUpstreamStats.bind(this)); 24 | this.client.on('stats/historical', this.onNewUpstreamStats.bind(this)); 25 | 26 | this.client.emit('stats/init', this.onStats.bind(this)); 27 | } 28 | 29 | populateProxyStats(proxy) { 30 | proxy.totalCapacityString = BurstProxy.capacityToString(proxy.totalCapacity); 31 | proxy.upstreamStats.forEach(upstreamStat => { 32 | upstreamStat.blockTime = upstreamStat.isBHD ? 300 : 240; 33 | 34 | const blockReward = upstreamStat.isBHD ? BurstProxy.getBHDBlockReward() : BurstProxy.getBurstBlockReward(upstreamStat.blockNumber); 35 | 36 | const capacityInTB = proxy.totalCapacity / 1024; 37 | const probabilityToFindBlock = capacityInTB / upstreamStat.netDiff; 38 | if (probabilityToFindBlock !== 0) { 39 | upstreamStat.timeToFindBlockInSeconds = 1 / probabilityToFindBlock * upstreamStat.blockTime; 40 | const timeToFindBlockInDays = upstreamStat.timeToFindBlockInSeconds / (60 * 60 * 24); 41 | upstreamStat.rewardsPerDay = blockReward / timeToFindBlockInDays; 42 | } 43 | 44 | upstreamStat.totalCapacity = proxy.totalCapacity; 45 | upstreamStat.totalCapacityString = proxy.totalCapacityString; 46 | upstreamStat.miner = proxy.miner; 47 | this.populateUpstreamStats(upstreamStat); 48 | }); 49 | } 50 | 51 | populateUpstreamStats(upstream) { 52 | upstream.performanceString = BurstProxy.capacityToString(upstream.estimatedCapacityInTB * 1024); 53 | } 54 | 55 | static capacityToString(capacityInGiB, precision = 2, correctUnit = true) { 56 | let capacity = capacityInGiB; 57 | let unit = 0; 58 | const units = correctUnit ? ['GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] : ['GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 59 | while (capacity >= 1024) { 60 | capacity /= 1024; 61 | unit += 1; 62 | } 63 | 64 | return `${capacity.toFixed(precision)} ${units[unit]}`; 65 | } 66 | 67 | onStats(proxies) { 68 | proxies.forEach(proxy => { 69 | this.populateProxyStats(proxy); 70 | }); 71 | 72 | this.stats = proxies; 73 | } 74 | 75 | onNewProxyStats(proxyName, proxyStats) { 76 | const stats = this.stats; 77 | if (!stats) { 78 | return; 79 | } 80 | const proxy = stats.find(proxy => proxy.name === proxyName); 81 | if (!proxy) { 82 | return; 83 | } 84 | Object.keys(proxyStats).forEach(key => { 85 | proxy[key] = proxyStats[key]; 86 | }); 87 | this.populateProxyStats(proxy); 88 | } 89 | 90 | onNewUpstreamStats(fullUpstreamName, upstreamStats) { 91 | const stats = this.stats; 92 | if (!stats) { 93 | return; 94 | } 95 | const upstream = stats 96 | .map(proxy => proxy.upstreamStats) 97 | .reduce((acc, curr) => acc.concat(curr), []) 98 | .find(upstream => upstream.fullName === fullUpstreamName); 99 | if (!upstream) { 100 | return; 101 | } 102 | Object.keys(upstreamStats).forEach(key => { 103 | upstream[key] = upstreamStats[key]; 104 | }); 105 | this.populateUpstreamStats(upstream); 106 | } 107 | 108 | cleanup() { 109 | this.client.disconnect(); 110 | super.cleanup(); 111 | } 112 | }; 113 | -------------------------------------------------------------------------------- /api/lib/miner/chia-archiver.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const Miner = require('./miner'); 4 | 5 | module.exports = class ChiaArchiver extends Miner { 6 | onInit() { 7 | this.client = axios.create({ 8 | baseURL: `${this.device.hostname}/api`, 9 | }); 10 | super.onInit(); 11 | } 12 | 13 | async updateStats() { 14 | try { 15 | const { data: stats } = await this.client.get('stats'); 16 | this.stats = stats; 17 | } catch (err) { 18 | this.stats = null; 19 | console.error(`[${this.device.name} :: ChiaArchiver] => ${err.message}`); 20 | } 21 | } 22 | 23 | getStats() { 24 | return Object.assign(super.getStats(), { stats: this.stats }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /api/lib/miner/chia-farmer.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const Miner = require('./miner'); 4 | 5 | module.exports = class ChiaFarmer extends Miner { 6 | onInit() { 7 | this.client = axios.create({ 8 | baseURL: this.device.hostname, 9 | }); 10 | super.onInit(); 11 | } 12 | 13 | async updateStats() { 14 | try { 15 | const { data: challenges } = await this.client.post('/get_latest_challenges', {}); 16 | const { data: connections } = await this.client.post('/get_connections', {}); 17 | this.stats = { 18 | challenges: challenges.latest_challenges, 19 | connections: connections.connections, 20 | }; 21 | } catch (err) { 22 | this.stats = null; 23 | console.error(`[${this.device.name} :: ChiaFarmer] => ${err.message}`); 24 | } 25 | } 26 | 27 | getStats() { 28 | return Object.assign(super.getStats(), { stats: this.stats }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /api/lib/miner/chia-miner.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const Miner = require('./miner'); 4 | 5 | module.exports = class ChiaMiner extends Miner { 6 | onInit() { 7 | this.client = axios.create({ 8 | baseURL: `${this.device.hostname}/api`, 9 | }); 10 | super.onInit(); 11 | } 12 | 13 | async updateStats() { 14 | try { 15 | const { data: stats } = await this.client.get('stats'); 16 | this.stats = stats; 17 | } catch (err) { 18 | this.stats = null; 19 | console.error(`[${this.device.name} :: ChiaMiner] => ${err.message}`); 20 | } 21 | } 22 | 23 | getStats() { 24 | return Object.assign(super.getStats(), { stats: this.stats }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /api/lib/miner/chia-plotter.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const Miner = require('./miner'); 4 | 5 | module.exports = class ChiaPlotter extends Miner { 6 | onInit() { 7 | this.client = axios.create({ 8 | baseURL: `${this.device.hostname}/api`, 9 | }); 10 | super.onInit(); 11 | } 12 | 13 | async updateStats() { 14 | try { 15 | const { data: stats } = await this.client.get('stats'); 16 | this.stats = stats; 17 | } catch (err) { 18 | this.stats = null; 19 | console.error(`[${this.device.name} :: ChiaPlotter] => ${err.message}`); 20 | } 21 | } 22 | 23 | getStats() { 24 | return Object.assign(super.getStats(), { stats: this.stats }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /api/lib/miner/creep-miner.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws'); 2 | const ReconnectingWebSocket = require('reconnecting-websocket'); 3 | const bytes = require('bytes'); 4 | const Miner = require('./miner'); 5 | 6 | const reconnectInterval = 60 * 60 * 1000; // 1 hr 7 | 8 | module.exports = class CreepMiner extends Miner { 9 | getStats() { 10 | return Object.assign(super.getStats(), {stats: this.stats}); 11 | } 12 | 13 | onInit() { 14 | const isBHD = this.device.name.toLowerCase().indexOf('bhd') !== -1; 15 | this.blockTime = isBHD ? 300 : 240; 16 | 17 | this.client = new ReconnectingWebSocket(`ws://${this.device.hostname}`, [], { 18 | WebSocket, 19 | }); 20 | 21 | this.client.addEventListener('message', this.onMessage.bind(this)); 22 | // Update the total plotSize regularly 23 | setInterval(() => this.client.reconnect(), reconnectInterval); 24 | } 25 | 26 | cleanup() { 27 | this.client.close(); 28 | super.cleanup(); 29 | } 30 | 31 | onMessage(message) { 32 | const data = message.data; 33 | if (!data) { 34 | return; 35 | } 36 | if (data === 'ping') { 37 | return; 38 | } 39 | const res = JSON.parse(data); 40 | switch (res.type) { 41 | case 'new block': 42 | this.stats.block = res.block; 43 | this.stats.blockStart = parseInt(res.startTime, 10); 44 | this.stats.difficulty = parseInt(res.difficulty, 10); 45 | this.stats.blocksWon = parseInt(res.blocksWon, 10); 46 | this.stats.roundsSubmitted = parseInt(res.nRoundsSubmitted, 10); 47 | this.stats.numHistoricals = parseInt(res.numHistoricals, 10); 48 | this.stats.bestDL = null; 49 | this.stats.dlBelowTenMinutes = res.bestDeadlines.filter(dl => parseInt(dl[1], 10) < 600).length; 50 | const probabilityToFindBlock = (this.stats.totalPlotSizeInTB || 0) / this.stats.difficulty; 51 | if (probabilityToFindBlock !== 0) { 52 | this.stats.timeToFindBlockInSeconds = 1 / probabilityToFindBlock * this.blockTime; 53 | } 54 | break; 55 | case 'nonce found': 56 | case 'nonce found (too high)': 57 | const dl = parseInt(res.deadlineNum, 10); 58 | if (!this.stats.bestDL || dl < this.stats.bestDL) { 59 | this.stats.bestDL = dl; 60 | } 61 | break; 62 | case 'nonce confirmed': 63 | case 'nonce submitted': 64 | break; 65 | case 'config': 66 | this.stats.totalPlotSize = res.totalPlotSize; 67 | this.stats.totalPlotSizeInTB = bytes(res.totalPlotSize) / (1000 * 1000 * 1000 * 1000); 68 | break; 69 | default: 70 | break; 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /api/lib/miner/group.js: -------------------------------------------------------------------------------- 1 | const minerUtil = require('./miner-util'); 2 | 3 | module.exports = class Group { 4 | 5 | constructor(options = {}) { 6 | this.miner = []; 7 | 8 | this.minerConfigs = options.minerConfigs; 9 | this.groupConfig = options.groupConfig; 10 | this.onInit(); 11 | } 12 | 13 | getStats() { 14 | return { 15 | devices: this.miner.map(miner => miner.getStats()), 16 | name: this.groupConfig.name, 17 | }; 18 | } 19 | 20 | shouldBeDisplayed() { 21 | return this.groupConfig.display; 22 | } 23 | 24 | setUpMiner() { 25 | // start miner 26 | this.miner = this.minerConfigs.map((miner) => { 27 | const Class = minerUtil.getClassForMinerType(miner.type); 28 | return new Class({device: miner, interval: this.groupConfig.interval}); 29 | }); 30 | } 31 | 32 | cleanup() { 33 | this.miner.map(miner => miner.cleanup()); 34 | this.miner = []; 35 | } 36 | 37 | onInit() { 38 | this.setUpMiner(); 39 | } 40 | }; -------------------------------------------------------------------------------- /api/lib/miner/miner-manager.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | const axios = require('axios'); 3 | const Miner = require('./miner'); 4 | const problemService = require('../services/problem-service'); 5 | 6 | module.exports = class MinerManager extends Miner { 7 | 8 | async updateStats() { 9 | try { 10 | const agent = new https.Agent({ 11 | rejectUnauthorized: false 12 | }); 13 | const minerData = await axios.get(`${this.device.hostname}/api/mining/stats`, {httpsAgent: agent}); 14 | await problemService.handleProblem(this.constructOnlineProblem()); 15 | await this.checkResult(minerData.data); 16 | this.stats = minerData.data.entries; 17 | } catch(err) { 18 | this.stats = null; 19 | console.error(`[${this.device.name} :: Miner-Manager] => ${err.message}`); 20 | await problemService.handleProblem(this.constructOfflineProblem()); 21 | } 22 | } 23 | 24 | getStats() { 25 | return Object.assign(super.getStats(), {entries: this.stats}); 26 | } 27 | 28 | async checkResult(result) { 29 | // check miner running 30 | const status = (result.entries && !Object.keys(result.entries).length) ? 'Problem' : 'OK'; 31 | await problemService.handleProblem({ 32 | type: 'item', 33 | status, 34 | descriptor: 'Number', 35 | item: { 36 | name: 'running miners', 37 | value: Object.keys(result.entries).length, 38 | highLow: 'low' 39 | }, 40 | device: this.device, 41 | }); 42 | 43 | // check temp if supported 44 | 45 | // check fan speed if supported 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /api/lib/miner/miner-util.js: -------------------------------------------------------------------------------- 1 | const MinerManager = require('./miner-manager'); 2 | const CreepMiner = require('./creep-miner'); 3 | const BurstProxy = require('./burst-proxy'); 4 | const Storj = require('./storj'); 5 | const ChiaPlotter = require('./chia-plotter'); 6 | const ChiaMiner = require('./chia-miner'); 7 | const ChiaFarmer = require('./chia-farmer'); 8 | const ChiaArchiver = require('./chia-archiver'); 9 | const Rclone = require('./rclone') 10 | 11 | function getClassForMinerType(type) { 12 | switch(type) { 13 | case 'miner-agent': 14 | return MinerManager; 15 | case 'storj': 16 | return Storj; 17 | case 'chia-plotter': 18 | return ChiaPlotter; 19 | case 'chia-miner': 20 | return ChiaMiner; 21 | case 'chia-farmer': 22 | return ChiaFarmer; 23 | case 'chia-archiver': 24 | return ChiaArchiver; 25 | case 'creep-miner': 26 | return CreepMiner; 27 | case 'burst-proxy': 28 | return BurstProxy; 29 | case 'rclone': 30 | return Rclone; 31 | default: 32 | throw new Error(`No class matched '${type}'`); 33 | } 34 | } 35 | 36 | module.exports = { 37 | getClassForMinerType, 38 | }; 39 | -------------------------------------------------------------------------------- /api/lib/miner/miner.js: -------------------------------------------------------------------------------- 1 | module.exports = class Miner { 2 | 3 | constructor(options = {}) { 4 | this.stats = {}; 5 | this.device = options.device; 6 | this.interval = options.interval; 7 | this.onInit(); 8 | } 9 | 10 | getStats() { 11 | return { 12 | type: this.device.type, 13 | name: this.device.name, 14 | hostname: this.device.hostname, 15 | }; 16 | } 17 | 18 | async updateStats() {} 19 | 20 | constructOnlineProblem() { 21 | return { 22 | type: 'device', 23 | status: 'OK', 24 | device: this.device, 25 | text: 'Up', 26 | }; 27 | } 28 | 29 | constructOfflineProblem() { 30 | return { 31 | type: 'device', 32 | status: 'Problem', 33 | device: this.device, 34 | text: 'Down', 35 | }; 36 | } 37 | 38 | cleanup() { 39 | if (this.runningInterval) { 40 | clearInterval(this.runningInterval); 41 | this.runningInterval = null; 42 | } 43 | this.stats = {}; 44 | } 45 | 46 | onInit() { 47 | this.updateStats(); 48 | this.runningInterval = setInterval(this.updateStats.bind(this), this.interval); 49 | } 50 | }; -------------------------------------------------------------------------------- /api/lib/miner/rclone.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | const Capacity = require('../capacity') 4 | const Miner = require('./miner') 5 | 6 | const shortenString = ((string, maxLength = 20) => { 7 | if (string.length <= maxLength) { 8 | return string 9 | } 10 | const halfMaxLength = Math.floor(maxLength / 2) 11 | 12 | return `${string.slice(0, halfMaxLength)}..${string.slice(-halfMaxLength)}` 13 | }) 14 | 15 | module.exports = class Rclone extends Miner { 16 | onInit() { 17 | this.apiClient = axios.create({ 18 | baseURL: this.device.hostname, 19 | }) 20 | super.onInit(); 21 | } 22 | 23 | async updateStats() { 24 | try { 25 | const { data } = await this.apiClient.post(`/core/stats`) 26 | 27 | const totalGib = Capacity.fromBytes(data.totalBytes).capacityInGib; 28 | const transferredGib = Capacity.fromBytes(data.bytes).capacityInGib; 29 | const percentage = transferredGib.dividedBy(totalGib).multipliedBy(100) 30 | 31 | this.stats = { 32 | totalGib: totalGib.toNumber(), 33 | transferredGib: transferredGib.toNumber(), 34 | percentage: percentage.toNumber(), 35 | speedInMibPerSec: Capacity.fromBytes(data.speed).toMiB().toNumber(), 36 | transfers: data.transferring.map(transfer => { 37 | const transferTotalGib = Capacity.fromBytes(transfer.size).capacityInGib; 38 | const transferTransferredGib = Capacity.fromBytes(transfer.bytes).capacityInGib; 39 | const percentage = transferTransferredGib.dividedBy(transferTotalGib).multipliedBy(100) 40 | 41 | return { 42 | shortName: shortenString(transfer.name, 50), 43 | fileName: transfer.name, 44 | totalGib: transferTotalGib.toNumber(), 45 | transferredGib: transferTransferredGib.toNumber(), 46 | percentage: percentage.toNumber(), 47 | speedInMibPerSec: Capacity.fromBytes(transfer.speed).toMiB().toNumber(), 48 | } 49 | }), 50 | } 51 | } catch(err) { 52 | this.stats = null; 53 | console.error(`[${this.device.name} :: Rclone] => ${err.message}`) 54 | } 55 | } 56 | 57 | getStats() { 58 | return Object.assign( 59 | super.getStats(), 60 | { 61 | stats: this.stats, 62 | id: this.device.id, 63 | } 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /api/lib/nicehash-api.js: -------------------------------------------------------------------------------- 1 | const { createHmac } = require('crypto'); 2 | const querystring = require('querystring'); 3 | const axios = require('axios'); 4 | const { v4: uuidv4 } = require('uuid'); 5 | 6 | const API_URL = 'https://api2.nicehash.com'; 7 | 8 | class NicehashApi { 9 | constructor({ 10 | apiKey, 11 | apiSecret, 12 | organizationId, 13 | apiUrl = API_URL, 14 | } = {}) { 15 | this.apiKey = apiKey; 16 | this.apiSecret = apiSecret; 17 | this.organizationId = organizationId; 18 | this.client = axios.create({ 19 | baseURL: apiUrl, 20 | headers: { 21 | 'User-Agent': 'NicehashApi/1.0.0', 22 | 'X-User-Agent': 'NicehashApi/1.0.0', 23 | }, 24 | }); 25 | this.client.interceptors.request.use(this._onBeforeRequestSent.bind(this), (err) => Promise.reject(err)); 26 | } 27 | 28 | async getTime() { 29 | const { data: { serverTime } } = await this.client.get('/api/v2/time', { isPublic: true }); 30 | 31 | return serverTime; 32 | } 33 | 34 | async getMiningAlgorithmsStats() { 35 | const { data: { algorithms } } = await this.client.get('/main/api/v2/mining/algo/stats'); 36 | 37 | return algorithms; 38 | } 39 | 40 | async getMiningAlgorithms() { 41 | const { data: { miningAlgorithms } } = await this.client.get('/main/api/v2/mining/algorithms', { isPublic: true }); 42 | 43 | return miningAlgorithms; 44 | } 45 | 46 | async getMiningGroups() { 47 | const { data: { groups } } = await this.client.get('/main/api/v2/mining/groups/list', { 48 | params: { 49 | extendedResponse: true, 50 | }, 51 | }); 52 | 53 | return groups; 54 | } 55 | 56 | async getMiningRig({ rigId }) { 57 | const { data } = await this.client.get(`/main/api/v2/mining/rig2/${rigId}`); 58 | 59 | return data; 60 | } 61 | 62 | async getMiningStats({ page = 0, limit = 25 } = {}) { 63 | const { data } = await this.client.get('/main/api/v2/mining/rigs2', { 64 | params: { 65 | page, 66 | size: limit, 67 | }, 68 | }); 69 | 70 | return data; 71 | } 72 | 73 | async getMiningPayouts({ page = 0, limit = 25 } = {}) { 74 | const { data: { list } } = await this.client.get('/main/api/v2/mining/rigs/payouts', { 75 | params: { 76 | page, 77 | size: limit, 78 | }, 79 | }); 80 | 81 | return list; 82 | } 83 | 84 | async getAccount() { 85 | const { data } = await this.client.get('/main/api/v2/accounting/accounts2', { 86 | params: { 87 | extendedResponse: true, 88 | }, 89 | }); 90 | 91 | return data; 92 | } 93 | 94 | _onBeforeRequestSent(config) { 95 | if (config.isPublic) { 96 | return config; 97 | } 98 | 99 | config.headers = Object.assign(config.headers, this._getAuthenticatedRequestHeaders({ 100 | method: config.method.toUpperCase(), 101 | path: config.url, 102 | params: config.params, 103 | body: config.data, 104 | })); 105 | 106 | return config; 107 | } 108 | 109 | _getAuthenticatedRequestHeaders(request) { 110 | const nonce = uuidv4(); 111 | const time = Date.now(); 112 | 113 | return { 114 | 'X-Time': time, 115 | 'X-Nonce': nonce, 116 | 'X-Organization-Id': this.organizationId, 117 | 'X-Request-Id': nonce, 118 | 'X-Auth': this._createXAuthHeader({ time, nonce, request }), 119 | }; 120 | } 121 | 122 | _createXAuthHeader({ time, nonce, request }) { 123 | return `${this.apiKey}:${this._createHmacSignature({ time, nonce, request })}`; 124 | } 125 | 126 | _createHmacSignature({ time, nonce, request }) { 127 | const hmac = createHmac('sha256', this.apiSecret); 128 | hmac.update(`${this.apiKey}\0${time}\0${nonce}\0\0${this.organizationId}\0\0${request.method}\0${request.path}\0`); 129 | if (request.params) { 130 | hmac.update(querystring.encode(request.params)); 131 | } 132 | if (request.body) { 133 | hmac.update(`\0${JSON.stringify(request.body)}`); 134 | } 135 | 136 | return hmac.digest('hex'); 137 | } 138 | } 139 | 140 | module.exports = NicehashApi; 141 | -------------------------------------------------------------------------------- /api/lib/rates/coingecko.js: -------------------------------------------------------------------------------- 1 | const superagent = require('superagent'); 2 | 3 | const util = require('../util'); 4 | 5 | class CoinGecko { 6 | constructor() { 7 | this.interval = 60 * 60 * 1000; 8 | this.currency = 'EUR'; 9 | this.baseUrl = 'https://api.coingecko.com/api/v3'; 10 | this.rates = []; 11 | 12 | this.running = false; 13 | this.init(); 14 | } 15 | 16 | getRates(symbol) { 17 | if (symbol.toLowerCase() === 'eur') { 18 | return [{ current_price: 1 }]; 19 | } 20 | if (symbol.toLowerCase() === 'burst') { 21 | symbol = 'signa'; 22 | } 23 | 24 | return this.rates.filter(rate => rate.symbol === symbol.toLowerCase()); 25 | } 26 | 27 | async updateRates() { 28 | if (this.running) { 29 | return; 30 | } 31 | this.running = true; 32 | let page = 1; // what the hell coingecko? page 0 and 1 are the same 33 | const limit = 250; 34 | let allRates = []; 35 | let rates = []; 36 | do { 37 | try { 38 | rates = await this.doApiCall('coins/markets', {vs_currency: this.currency.toLowerCase(), per_page: limit, page, order: 'id_asc'}); 39 | allRates = allRates.concat(rates); 40 | page += 1; 41 | await util.sleep(30); 42 | } catch (err) { 43 | console.error(`[CoinGecko] => ${err.message}`); 44 | await util.sleep(60); 45 | } 46 | } while (rates.length === limit); 47 | this.rates = allRates; 48 | this.running = false; 49 | } 50 | 51 | async init() { 52 | await this.updateRates(); 53 | setInterval(this.updateRates.bind(this), this.interval); 54 | } 55 | 56 | async doApiCall(endpoint, params = {}) { 57 | const res = await superagent.get(`${this.baseUrl}/${endpoint}`).query(params).timeout({ 58 | response: 60 * 1000, // Wait 1 min for the server to start sending, 59 | deadline: 90 * 1000, // but allow 1:30 min for the request to finish loading. 60 | }); 61 | 62 | return res.body; 63 | } 64 | } 65 | 66 | module.exports = new CoinGecko(); 67 | -------------------------------------------------------------------------------- /api/lib/services/mail-service.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); 2 | const config = require('../../modules/configModule'); 3 | 4 | let transport = null; 5 | 6 | function onInit() { 7 | createTransport(); 8 | } 9 | 10 | function createTransport() { 11 | transport = nodemailer.createTransport(config.config.mailConfig || {}); 12 | } 13 | 14 | async function sendMail(options) { 15 | let result = null; 16 | try { 17 | result = await transport.sendMail(options); 18 | } catch(err) { 19 | console.error(`[Mail] Sending mail failed: ${err.message}`); 20 | } 21 | 22 | return result; 23 | } 24 | 25 | async function verifyTransport(){ 26 | let status = null; 27 | try { 28 | status = await transport.verify(); 29 | } catch(err) { 30 | console.error(`[Mail] SMTP Connection Verification failed: ${err.message}`); 31 | return false; 32 | } 33 | // TODO: use status? 34 | return true; 35 | } 36 | 37 | onInit(); 38 | 39 | module.exports = { 40 | sendMail, 41 | verifyTransport, 42 | createTransport, 43 | }; -------------------------------------------------------------------------------- /api/lib/stats.js: -------------------------------------------------------------------------------- 1 | // Group 2 | const Group = require('../lib/miner/group'); 3 | 4 | // Util 5 | const util = require('../lib/util'); 6 | const dashboardUtil = require('../lib/dashboards/dashboard-util'); 7 | const configModule = require(__basedir + 'api/modules/configModule'); 8 | 9 | module.exports = class Stats { 10 | 11 | constructor() { 12 | this.instances = { 13 | dashboard: [], 14 | group: [], 15 | }; 16 | this.onInit(); 17 | } 18 | 19 | async initializeAllDashboards() { 20 | console.log(`initializing all dashboards..`); 21 | const dashboards = configModule.config.dashboardData 22 | .filter(dashboard => dashboard.enabled); 23 | const nonNicehashDashboards = dashboards.filter(dashboard => dashboard.type !== 'nicehash'); 24 | const nicehashDashboards = dashboards.filter(dashboard => dashboard.type === 'nicehash'); 25 | this.instances.dashboard = []; 26 | for (const dashboard of nonNicehashDashboards) { 27 | const Class = dashboardUtil.getClassForDashboardType(dashboard.type); 28 | this.instances.dashboard.push(new Class({ dashboard })); 29 | // await util.sleep(1); 30 | } 31 | // start nicehash dashboards with 31 sec delays to workaround nicehash api limits 32 | for (const dashboard of nicehashDashboards) { 33 | const Class = dashboardUtil.getClassForDashboardType(dashboard.type); 34 | this.instances.dashboard.push(new Class({ dashboard })); 35 | await util.sleep(31); 36 | } 37 | console.log(`initialized all dashboards`); 38 | } 39 | 40 | initializeAllGroups() { 41 | console.log(`initializing all groups..`); 42 | this.instances.group = configModule.config.groups 43 | .filter(group => group.enabled) 44 | .map((group) => { 45 | const devicesInGroup = configModule.config.devices 46 | .filter(device => device.enabled && device.group === group.id); 47 | const options = { 48 | groupConfig: JSON.parse(JSON.stringify(group)), 49 | minerConfigs: devicesInGroup, 50 | }; 51 | options.groupConfig.interval = (group.interval ? group.interval : configModule.config.interval) * 1000; 52 | return new Group(options); 53 | }); 54 | console.log(`initialized all groups`); 55 | } 56 | 57 | getStats() { 58 | const entries = this.instances.group 59 | .filter(group => group.shouldBeDisplayed()) 60 | .map(instance => instance.getStats()); 61 | const dashboardData = this.instances.dashboard.map(instance => instance.getStats()); 62 | dashboardData.sort(function (a, b) { 63 | if (a.name < b.name) return -1; 64 | if (a.name > b.name) return 1; 65 | return 0; 66 | }); 67 | 68 | return { entries, dashboardData }; 69 | } 70 | 71 | cleanup() { 72 | this.instances.dashboard.map(dashboard => dashboard.cleanup()); 73 | this.instances.group.map(group => group.cleanup()); 74 | this.instances = { 75 | dashboard: [], 76 | group: [], 77 | }; 78 | } 79 | 80 | async onInit() { 81 | this.initializeAllGroups(); 82 | await this.initializeAllDashboards(); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /api/lib/util.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | const axios = require('axios'); 3 | 4 | const agent = new https.Agent({ 5 | rejectUnauthorized: false 6 | }); 7 | 8 | async function getUrl(url) { 9 | const result = await axios.get(url, {httpsAgent: agent, timeout: 2 * 60 * 1000}); 10 | return result.data; 11 | } 12 | 13 | async function postUrl(url, data) { 14 | const result = await axios.post(url, data, {httpsAgent: agent, timeout: 2 * 60 * 1000}); 15 | return result.data; 16 | } 17 | 18 | function sleep(seconds) { 19 | return new Promise(resolve => setTimeout(resolve, seconds * 1000)); 20 | } 21 | 22 | function parseHashrate(hashrateString) { 23 | const hashrateArr = hashrateString.split(' '); 24 | if (hashrateArr.length !== 2) { 25 | throw new Error(`can't parse hashrate string: ${hashrateString}`); 26 | } 27 | let hashrate = parseFloat(hashrateArr[0]); 28 | switch(hashrateArr[1]) { 29 | case 'PH': 30 | case 'PH/s': 31 | hashrate *= 1000; 32 | case 'TH': 33 | case 'TH/s': 34 | hashrate *= 1000; 35 | case 'GH': 36 | case 'GH/s': 37 | hashrate *= 1000; 38 | case 'MH': 39 | case 'MH/s': 40 | hashrate *= 1000; 41 | case 'KH': 42 | case 'KH/s': 43 | hashrate *= 1000; 44 | case 'H': 45 | case 'H/s': 46 | break; 47 | } 48 | 49 | return hashrate; 50 | } 51 | 52 | module.exports = { 53 | getUrl, 54 | postUrl, 55 | sleep, 56 | parseHashrate, 57 | }; 58 | -------------------------------------------------------------------------------- /api/modules/configModule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const colors = require('colors/safe'); 4 | const fs = require('fs'); 5 | 6 | const configPath='data/settings.json'; 7 | 8 | if (!fs.existsSync('data')){ 9 | fs.mkdirSync('data'); 10 | } 11 | const config = module.exports = { 12 | config: { 13 | interval:null, 14 | devices:[], 15 | groups:[], 16 | mailConfig:null, 17 | mailTo:null, 18 | dashboardData:[] 19 | }, 20 | configNonPersistent:{ 21 | types:[ 22 | 'miner-agent', 23 | 'creep-miner', 24 | 'burst-proxy', 25 | 'storj', 26 | 'chia-plotter', 27 | 'chia-miner', 28 | 'chia-farmer', 29 | 'chia-archiver', 30 | 'rclone', 31 | ], 32 | dashboardTypes:[ 33 | 'bitcoinBalance', 34 | 'bitmart-balance', 35 | 'signum-balance', 36 | 'counterpartyBalance', 37 | 'cryptoidBalance', 38 | 'dashboard-api', 39 | 'colab-manager-stats-collection', 40 | 'ethBalance', 41 | 'genericMPOS', 42 | 'hdpool', 43 | 'hdpool-control', 44 | 'hpool', 45 | 'miningpoolhub', 46 | 'foxy-pool', 47 | 'foxy-pool-v2', 48 | 'nicehash', 49 | 'nicehashBalance', 50 | 'node-cryptonote-pool', 51 | 'snipa-nodejs-pool', 52 | 'generic-wallet', 53 | 'bitbean-wallet', 54 | 'bhd-wallet', 55 | 'disc-wallet', 56 | 'burst-wallet', 57 | 'boom-wallet', 58 | 'chia-wallet', 59 | 'wallet-agent', 60 | 'coinbase', 61 | 'yiimp', 62 | 'all-the-blocks-balance', 63 | 'bhd-balance', 64 | ] 65 | }, 66 | getConfig: function () { 67 | const obj=config.config; 68 | obj.types=config.configNonPersistent.types; 69 | obj.layouts=config.configNonPersistent.layouts; 70 | obj.dashboardTypes=config.configNonPersistent.dashboardTypes; 71 | return obj; 72 | }, 73 | setConfig: function (newConfig) { 74 | delete newConfig.types; 75 | delete newConfig.layouts; 76 | delete newConfig.dashboardTypes; 77 | config.config = newConfig; 78 | }, 79 | saveConfig: function () { 80 | console.log(colors.grey('writing config to file..')); 81 | fs.writeFile(configPath, JSON.stringify(config.config,null,2), function (err) { 82 | if (err) { 83 | return console.error(err); 84 | } 85 | }); 86 | }, 87 | loadConfig: function () { 88 | fs.stat(configPath, function (err, stat) { 89 | if (err === null) { 90 | fs.readFile(configPath, 'utf8', function (err, data) { 91 | if (err) throw err; 92 | config.config = JSON.parse(data); 93 | if(config.config.groups===undefined) 94 | config.config.groups=[]; 95 | if(config.config.dashboardData===undefined) 96 | config.config.dashboardData=[]; 97 | if(config.config.mailConfig===undefined) 98 | config.config.mailConfig=null; 99 | // migrations 100 | config.config.devices 101 | .filter(device => typeof device.group === 'string') 102 | .map((device) => { 103 | const group = config.config.groups.find(group => group.name === device.group); 104 | if (group) { 105 | device.group = group.id; 106 | } 107 | }); 108 | config.saveConfig(); 109 | }); 110 | } else if (err.code === 'ENOENT') { 111 | //default conf 112 | config.config.interval=30; 113 | config.config.dashboardData=[]; 114 | config.saveConfig(); 115 | setTimeout(function(){ 116 | config.loadConfig(); 117 | },500); 118 | } 119 | }); 120 | } 121 | }; 122 | console.log('initializing, please wait...'); 123 | config.loadConfig(); 124 | -------------------------------------------------------------------------------- /api/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | module.exports = function(app) { 6 | const router = express.Router(); 7 | 8 | const configController = require(__basedir + 'api/controllers/configController'); 9 | const statsController = require(__basedir + 'api/controllers/statsController'); 10 | 11 | router.get('/config', configController.getConfig); 12 | router.post('/config', configController.setConfig); 13 | router.get('/config/layout', configController.getLayout); 14 | router.get('/config/verifyTransport', configController.verifyTransport); 15 | router.post('/config/update', configController.update); 16 | router.post('/config/updateMiner', configController.updateMiner); 17 | router.post('/config/updateAgent', configController.updateAgent); 18 | router.post('/config/rebootSystem', configController.rebootSystem); 19 | router.post('/config/restartShares', configController.restartShares); 20 | 21 | router.get('/mining/stats', statsController.getStats); 22 | 23 | app.use('/api', router); 24 | }; 25 | -------------------------------------------------------------------------------- /app/views/partials/components/balances/balance-summary.html: -------------------------------------------------------------------------------- 1 |
Balance | 8 |Fiat balance | 9 |
---|---|
14 | {{$ctrl.getTotalForBalances(dashboard.balances, 'balance') | customnumber:6}} {{dashboard.ticker}}
15 |
16 |
17 | 18 | {{balance.name}}{{balance.name}}: {{balance.data.balance | customnumber:6}} {{dashboard.ticker}} 19 | 20 | 21 | |
22 |
23 |
24 | {{$ctrl.getTotalForBalances(dashboard.balances, 'balanceFiat') | customnumber:2}} €
25 |
26 |
27 | 28 | {{balance.data.balanceFiat | customnumber:2}} € 29 | 30 | 31 | |
32 |
Total | 35 |{{$ctrl.getTotal('balanceFiat') | customnumber: 2}} € | 36 |
Wallet | 6 |Amount | 7 |
---|---|
12 | {{dashboard.name}} 13 | | 14 |15 | 16 | {{(dashboard.data.balance) | customnumber:8}} BTC 17 | 18 | | 19 |
Total | 22 |{{$ctrl.getFiatTotal() | customnumber: 2}} € | 23 |
Account | 6 |Amount | 7 |
---|---|
12 | {{dashboard.name}} 13 | | 14 |
15 |
16 |
17 | {{balance.available | customnumber:6}} {{balance.id}}
18 |
19 |
20 | 21 | 22 | {{balance.frozen | customnumber:6}} {{balance.id}} (L) 23 | 24 | 25 | 26 | 27 | 28 | 29 | |
30 |
Total | 33 |{{$ctrl.getFiatTotal() | customnumber: 2}} € | 34 |
Wallet | 6 |Amount | 7 |Token | 8 |
---|---|---|
13 | {{dashboard.name}} 14 | | 15 |
16 |
17 |
18 | {{entry.balance | customnumber:6}}
19 | 20 | 21 | |
22 |
23 | {{entry.asset}} 24 | |
25 |
Total | 28 |{{$ctrl.getFiatTotal() | customnumber: 2}} € | 29 |
Wallet | 6 |Amount | 7 |
---|---|
12 | {{dashboard.name}} 13 | | 14 |15 | 16 | {{dashboard.data.balance | customnumber:8}} {{dashboard.ticker}} 17 | 18 | | 19 |
Total | 22 |{{$ctrl.getFiatTotal() | customnumber: 2}} € | 23 |
Wallet | 6 |Amount | 7 |
---|---|
12 | {{dashboard.name}} 13 | | 14 |
15 |
16 |
17 | {{dashboard.data.eth.balance | customnumber: 8}} ETH
18 |
19 |
20 | 21 | 22 | 23 | {{entry.balance | customnumber:6}} {{entry.tokenInfo.symbol}} 24 | 25 | 26 | 27 | 28 | 29 | |
30 |
Total | 33 |{{$ctrl.getFiatTotal() | customnumber: 2}} € | 34 |
Wallet | 6 |Amount | 7 |
---|---|
{{dashboard.name}} | 12 |13 | 14 | {{dashboard.data.balance | customnumber:8}} BTC 15 | 16 | | 17 |
Total | 20 |{{$ctrl.getFiatTotal() | customnumber: 2}} € | 21 |
T | 6 |K | 7 |G | 8 |R | 9 |Total | 10 |Balances | 11 ||
---|---|---|---|---|---|---|
{{$ctrl.dashboard.data.training}} | 16 |{{$ctrl.dashboard.data.keepalive}} | 17 |{{$ctrl.dashboard.data.graveyard}} | 18 |{{$ctrl.dashboard.data.removed}} | 19 |{{$ctrl.dashboard.data.total}} | 20 |
21 |
22 |
23 | {{$ctrl.dashboard.data.totalEth | customnumber:6}} ETH
24 |
25 | 26 | 27 | 28 | {{$ctrl.dashboard.data.totalStorj | customnumber:0:true}} STORJ 29 | 30 | |
31 | {{$ctrl.getFiatTotal() | customnumber: 2}} € | 32 |
Name | 12 |Block # | 13 |Elapsed | 14 |Best DL | 15 |Rounds | 16 |EC | 17 |Plot size | 18 |
---|---|---|---|---|---|---|
{{upstream.name}} | 23 |{{upstream.blockNumber}} | 24 |{{$ctrl.getTimeElapsedSinceLastBlock(upstream.roundStart)}} | 25 |{{$ctrl.getBestDeadlineString(upstream.bestDL)}} | 26 |27 | 28 | {{upstream.roundsWon}} / {{upstream.roundsSubmitted}} / {{upstream.totalRounds}} 29 | 30 | | 31 |{{upstream.performanceString}} | 32 |{{upstream.totalCapacityString}} | 33 |
Source | 14 |Destination | 15 |Speed | 16 |Elapsed | 17 |Progress | 18 |
---|---|---|---|---|
23 | {{transfer.ip}} 24 | | 25 |26 | {{transfer.destinationDirectory}} 27 | | 28 |29 | {{transfer.currSpeedInMibPerSecond.toFixed(2)}} MiB/s ({{transfer.writtenGib.toFixed(2)}} / {{transfer.sizeInGib.toFixed(2)}} GiB) 30 | | 31 |32 | {{$ctrl.getFormattedDuration(transfer.elapsedTimeInSeconds)}} 33 | | 34 |35 | {{transfer.progress.toFixed(2)}}% (ETA: {{$ctrl.getFormattedDuration(transfer.currRemainingTimeInSeconds)}}) 36 | | 37 |
40 | Total 41 | | 42 |43 | {{$ctrl.totalSpeedInMibPerSecond().toFixed(2)}} MiB/s ({{$ctrl.totalWrittenGib().toFixed(2)}} / {{$ctrl.totalSizeInGib().toFixed(2)}} GiB) 44 | | 45 |46 | Next transfer done in {{$ctrl.lowestEtaFormatted()}} 47 | | 48 |
Height | 14 |Challenge | 15 |Proofs | 16 |Best DL | 17 |
---|---|---|---|
22 | {{challenge.height}} 23 | | 24 |25 | {{challenge.challenge.substr(2, 10)}} 26 | | 27 |28 | {{challenge.estimates.length}} 29 | | 30 |31 | {{$ctrl.bestDLForChallenge(challenge)}} 32 | | 33 |
Machine | 14 |State | 15 |Capacity | 16 |Challenge | 17 |Elapsed | 18 |Scan Time | 19 |E | 20 |P | 21 |
---|---|---|---|---|---|---|---|
26 | {{miner.name}} 27 | | 28 |29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {{$ctrl.getState(miner.stats)}} 39 | | 40 |41 | {{(miner.stats.capacityInGib || 0) | capacity:'GiB'}} 42 | | 43 |44 | {{miner.stats.challengeHashStart || 'N/A'}} 45 | | 46 |47 | {{$ctrl.getFormattedElapsedDuration(miner.stats)}} 48 | | 49 |50 | {{(miner.stats.scanDurationInSeconds !== undefined ? miner.stats.scanDurationInSeconds.toFixed(2) + ' sec' : 'N/A')}} 51 | | 52 |53 | {{miner.stats.eligiblePlots || 0}} 54 | | 55 |56 | {{miner.stats.foundProofs || 0}} 57 | | 58 |
61 | Total 62 | | 63 |64 | {{$ctrl.totalCapacityInGib() | capacity:'GiB'}} 65 | | 66 |67 | | 68 |
Destination | 14 |Free Space | 15 |TiB / month | 16 |Phase | 17 |Operation | 18 |Progress | 19 ||
---|---|---|---|---|---|---|
24 | {{plotJob.destinationDir}} 25 | | 26 |27 | {{plotJob.freeSpaceOnDestinationDirInGib | capacity:'GiB'}} 28 | | 29 |30 | {{plotJob.expectedTibPerMonth | capacity:'TiB'}} / m 31 | | 32 |33 | N/A 34 | | 35 |36 | {{plotJob.currentPhase}} / {{plotJob.totalPhases}} 37 | | 38 |39 | {{plotJob.operation}} 40 | | 41 |42 | {{plotJob.progress.toFixed(2)}}% (ETA: {{$ctrl.getFormattedEta(plotJob)}}) 43 | | 44 |
47 | Total 48 | | 49 |50 | {{$ctrl.totalFreeSpace() | capacity:'GiB'}} 51 | | 52 |53 | {{$ctrl.totalExpectedTibPerMonth() | capacity:'TiB'}} / m 54 | | 55 |56 | Next plot done in {{$ctrl.getFormattedEta($ctrl.plotJobWithLowestEta())}} 57 | | 58 |
Name | 12 |Block # | 13 |Elapsed | 14 |Best DL | 15 |DL's submitted | 16 |Plot size | 17 |
---|---|---|---|---|---|
{{entry.name}} | 22 |{{entry.stats.block}} | 23 |{{$ctrl.getTimeElapsedSinceLastBlock(entry.stats.blockStart)}} | 24 |{{$ctrl.getBestDeadlineString(entry.stats.bestDL)}} | 25 |26 | 27 | {{entry.stats.dlBelowTenMinutes}} / {{entry.stats.roundsSubmitted}} / {{entry.stats.numHistoricals}} 28 | 29 | 30 | ({{((entry.stats.roundsSubmitted/entry.stats.numHistoricals) * 100).toFixed(2)}}%) 31 | 32 | | 33 |{{entry.stats.totalPlotSize || 0}} | 34 |
Machine | 14 |Transfers | 15 |Progress | 16 |||
---|---|---|---|---|
21 | {{rclone.name}} 22 | | 23 |24 | N/A 25 | | 26 |27 | N/A 28 | | 29 |
30 |
31 |
32 | {{transfer.shortName}}: {{transfer.speedInMibPerSec | capacity:'MiB'}}/s 33 | 34 | {{(transfer.percentage).toFixed(2)}}%, {{transfer.transferredGib | capacity:'GiB'}} / {{transfer.totalGib | capacity:'GiB'}} 35 | | ETA: {{$ctrl.getFormattedEta(transfer)}} 36 | 37 | 38 | 39 | 40 | |
41 |
42 | {{rclone.stats.speedInMibPerSec | capacity:'MiB'}}/s
43 | | ETA: {{$ctrl.getFormattedEta(rclone.stats)}} 44 | {{(rclone.stats.percentage).toFixed(2)}}%, {{rclone.stats.transferredGib | capacity:'GiB'}} / {{rclone.stats.totalGib | capacity:'GiB'}} 45 | |
46 |
49 | Total 50 | | 51 |52 | 53 | {{$ctrl.totalSpeedInMibPerSec() | capacity:'MiB'}}/s 54 | | ETA: {{$ctrl.getTotalFormattedEta()}} 55 | | {{$ctrl.totalProgress().toFixed(2)}}%, {{$ctrl.totalTransferredGib() | capacity:'GiB'}} / {{$ctrl.totalGib() | capacity:'GiB'}} 56 | 57 | | 58 |
Status | 14 |Space used | 15 |Bandwidth | 16 |Ingress | 17 |Egress | 18 |Pending | 19 ||||||
---|---|---|---|---|---|---|---|---|---|---|
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {{node.name}} 41 | 42 | 43 | 44 | 45 | | 46 |47 | N/A 48 | | 49 |50 | N/A 51 | | 52 |53 | N/A 54 | | 55 |56 | N/A 57 | | 58 |59 | N/A 60 | | 61 |62 | {{node.stats.diskSpace.used | bytes:2}} ({{(((node.stats.diskSpace.used + node.stats.diskSpace.trash) / node.stats.diskSpace.available) * 100).toFixed(0)}}%, {{node.diskUsageSpeed | bytes:1}}/s) 63 | | 64 |65 | {{node.stats.bandwidth.used | bytes:2}} 66 | | 67 |68 | {{node.stats.ingressSummary | bytes:2}} ({{node.ingressSpeed | bytes:1}}/s) 69 | | 70 |71 | {{node.stats.egressSummary | bytes:2}} ({{node.egressSpeed | bytes:1}}/s) 72 | | 73 |74 | {{(node.stats.estimatedPayoutFiat || 0).toFixed(2)}} € 75 | | 76 |
79 | Total 80 | | 81 |82 | {{$ctrl.totalSpaceUsed() | bytes:2}} ({{$ctrl.totalDiskUsageSpeed() | bytes:1}}/s) 83 | | 84 |85 | {{$ctrl.totalBandwidthUsed() | bytes:2}} 86 | | 87 |88 | {{$ctrl.totalIngress() | bytes:2}} ({{$ctrl.totalIngressSpeed() | bytes:1}}/s) 89 | | 90 |91 | {{$ctrl.totalEgress() | bytes:2}} ({{$ctrl.totalEgressSpeed() | bytes:1}}/s) 92 | | 93 |94 | {{$ctrl.totalEstimatedPayoutFiat().toFixed(2)}} € 95 | | 96 |
Coin | 13 |Pending | 14 |Pledged | 15 |Deadlines | 16 |EC | 17 |Capacity | 18 |Last Payout | 19 ||
---|---|---|---|---|---|---|---|
24 | 30 | {{pool.data.coin}} 31 | | 32 |33 | 34 | {{pool.data.miner.pending.toFixed(4)}} {{pool.data.coin}} 35 | 36 | | 37 |38 | 39 | {{pool.data.miner.pledge ? pool.data.miner.pledge.toFixed(2) + ' ' + pool.data.coin : 'N/A'}} 40 | 41 | | 42 |43 | {{pool.data.miner.deadlineCount ? pool.data.miner.deadlineCount : 'N/A'}} 44 | | 45 |46 | {{$ctrl.getFormattedCapacity((pool.data.miner.ec || 0) * 1024)}} 47 | | 48 |49 | {{$ctrl.getFormattedCapacity(pool.data.miner.reportedCapacity)}} 50 | | 51 |52 | {{pool.data.lastPayout.date}} 53 | 54 | ({{$ctrl.getFormattedAmount(pool.data.lastPayout.amount) |customnumber:4}} {{pool.data.coin}}) 55 | 56 | | 57 |58 | N/A 59 | | 60 |
Miner | 7 |8 | 9 | Unconfirmed 10 | 11 | | 12 |Balance | 13 |14 | 15 | Credited 24h 16 | 17 | | 18 |
---|---|---|---|
23 | {{dashboard.data.onlineCapacityString}}
24 |
25 |
26 |
31 | 27 | 28 | {{miner.name}}: {{miner.capacityString}} 29 | 30 | |
32 | 33 | 34 | {{(dashboard.data.expectedProfit || 0) | customnumber: 6}} {{dashboard.symbol}} ({{$ctrl.timeTillRoundFinishedInHours(dashboard.data.currentRoundEndDate)}}h) 35 | 36 | | 37 |
38 |
39 | {{dashboard.data.balance | customnumber: 6}} {{dashboard.symbol}}
40 |
41 | 42 | + {{dashboard.data.expectedProfitLastRound | customnumber: 6}} {{dashboard.symbol}} 43 | 44 | 45 | |
46 | 47 | 48 | {{dashboard.data.incomeLastDay | customnumber: 6}} {{dashboard.symbol}} 49 | 50 | | 51 |
54 |
55 |
56 | Total
57 |
58 |
59 | {{$ctrl.getTotal('onlineCapacity') | bytes:2}}
60 |
61 |
62 | |
63 | {{$ctrl.getTotal('expectedProfit') | customnumber: 6}} BHD {{$ctrl.getTotal('expectedProfitFiat') | customnumber: 2}} € |
64 | {{($ctrl.getTotal('balance') + $ctrl.getTotal('expectedProfitLastRound')) | customnumber: 6}} BHD {{($ctrl.getTotal('balanceFiat') + $ctrl.getTotal('expectedProfitLastRoundFiat')) | customnumber: 2}} € |
65 | {{$ctrl.getTotal('incomeLastDay') | customnumber: 6}} BHD {{$ctrl.getTotal('incomeLastDayFiat') | customnumber: 2}} € |
66 |
Miner | 7 |8 | 9 | Unconfirmed 10 | 11 | | 12 |Balance | 13 |14 | 15 | Credited 24h 16 | 17 | | 18 |
---|---|---|---|
23 | {{dashboard.data.onlineCapacityString}}
24 |
25 |
26 |
31 | 27 | 28 | {{miner.name}}: {{miner.capacityString}} 29 | 30 | |
32 | 33 | 34 | {{(dashboard.data.unconfirmed || 0) | customnumber: 6}} {{dashboard.symbol}} ({{$ctrl.timeTillRoundFinishedInHours(dashboard.data.lastPayedTs)}}h) 35 | 36 | | 37 |38 | 39 | {{dashboard.data.balance | customnumber: 6}} {{dashboard.symbol}} 40 | 41 | | 42 |43 | 44 | {{dashboard.data.incomeLastDay | customnumber: 6}} {{dashboard.symbol}} 45 | 46 | | 47 |
50 |
51 |
52 | Total
53 |
54 |
55 | {{$ctrl.getTotal('onlineCapacity') | bytes:2}}
56 |
57 |
58 | |
59 | {{$ctrl.getTotal('unconfirmed') | customnumber: 6}} BHD {{$ctrl.getTotal('unconfirmedFiat') | customnumber: 2}} € |
60 | {{$ctrl.getTotal('balance') | customnumber: 6}} BHD {{$ctrl.getTotal('balanceFiat') | customnumber: 2}} € |
61 | {{$ctrl.getTotal('incomeLastDay') | customnumber: 6}} BHD {{$ctrl.getTotal('incomeLastDayFiat') | customnumber: 2}} € |
62 |
Hashrate | 7 |Confirmed | 8 |Unconfirmed | 9 |Estimated Round Earnings | 10 |Homepage | 11 |
---|---|---|---|---|
16 | {{dashboard.data.hashrate | hashrate}}
17 |
18 |
19 | 20 | {{value.username}}: {{value.hashrate | hashrate}} (diff: {{value.difficulty}}) 22 | 23 | 24 | |
25 | 26 | 27 | {{dashboard.data.confirmed | customnumber:6}} {{dashboard.data.symbol}} 28 | 29 | | 30 |31 | 32 | {{dashboard.data.unconfirmed | customnumber:6}} {{dashboard.data.symbol}} 33 | 34 | | 35 |36 | 37 | {{dashboard.data.estimated | customnumber:6}} {{dashboard.data.symbol}} 38 | 39 | | 40 |41 | Pool 42 | | 43 |
Hashrate | 7 |Pending | 8 |Paid | 9 |Est. Daily Profit | 10 |Last Share | 11 |12 | 13 | Last Block 14 | 15 | | 16 |Homepage | 17 |
---|---|---|---|---|---|---|
{{dashboard.data.hashrate | hashrate:null:0}} | 22 |23 | 24 | {{dashboard.data.pending | customnumber: 8}} {{dashboard.data.symbol}} 25 | 26 | | 27 |28 | 29 | {{dashboard.data.paid | customnumber: 8}} {{dashboard.data.symbol}} 30 | 31 | | 32 |33 | 34 | {{dashboard.data.estimatedProfit | customnumber: 8}} {{dashboard.data.symbol}} 35 | 36 | | 37 |{{dashboard.data.lastShareSubmitted}} | 38 |{{dashboard.data.lastBlockFound}} | 39 |Pool | 40 |
Total | 43 |{{$ctrl.getNodeCryptonotePoolFiatTotal('pending') | customnumber: 2}} € | 44 |{{$ctrl.getNodeCryptonotePoolFiatTotal('paid') | customnumber: 2}} € | 45 |{{$ctrl.getNodeCryptonotePoolFiatTotal('estimatedProfit') | customnumber: 2}} € | 46 |47 | |
Hashrate | 7 |Balance | 8 |Unconfirmed | 9 |10 | 11 | Paid 24h 12 | 13 | | 14 |Homepage | 15 |
---|---|---|---|---|
20 | {{dashboard.data.hashrate | hashrate:null:0}}
21 |
22 |
23 | 24 | {{miner.password}}: {{miner.accepted | hashrate:null:0}} (diff: {{miner.difficulty}}) 26 | 27 | 28 | |
29 | 30 | 31 | {{dashboard.data.balance | customnumber: 6}} {{dashboard.data.symbol}} 32 | 33 | | 34 |35 | 36 | {{dashboard.data.unconfirmed | customnumber: 6}} {{dashboard.data.symbol}} 37 | 38 | | 39 |40 | 41 | {{dashboard.data.paid24h | customnumber: 6}} {{dashboard.data.symbol}} 42 | 43 | | 44 |45 | Pool 46 | | 47 |