Merge with last version of 'zone117x/node-cryptonote-pool'
commit
96ae55de70
|
@ -1,3 +1,4 @@
|
|||
node_modules/
|
||||
.idea/
|
||||
config.json
|
||||
config.json
|
||||
logs/
|
||||
|
|
31
README.md
31
README.md
|
@ -232,6 +232,7 @@ Explanation for each field:
|
|||
"enabled": true,
|
||||
"interval": 600, //how often to run in seconds
|
||||
"maxAddresses": 50, //split up payments if sending to more than this many addresses
|
||||
"mixin": 3, //number of transactions yours is indistinguishable from
|
||||
"transferFee": 5000000000, //fee to pay for each transaction
|
||||
"minPayment": 100000000000, //miner balance required before sending payment
|
||||
"denomination": 100000000000 //truncate to this precision and store remainder
|
||||
|
@ -247,8 +248,9 @@ Explanation for each field:
|
|||
/* Block depth required for a block to unlocked/mature. Found in daemon source as
|
||||
the variable CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW */
|
||||
"depth": 60,
|
||||
"poolFee": 2, //2% pool fee
|
||||
"devDonation": 0.1 //0.1% donation to send to pool dev - only works with Monero
|
||||
"poolFee": 1.8, //1.8% pool fee (2% total fee total including donations)
|
||||
"devDonation": 0.1, //0.1% donation to send to pool dev - only works with Monero
|
||||
"coreDevDonation": 0.1 //0.1% donation to send to core devs - only works with Monero
|
||||
},
|
||||
|
||||
/* AJAX API used for front-end website. */
|
||||
|
@ -256,7 +258,10 @@ Explanation for each field:
|
|||
"enabled": true,
|
||||
"hashrateWindow": 600, //how many second worth of shares used to estimate hash rate
|
||||
"updateInterval": 3, //gather stats and broadcast every this many seconds
|
||||
"port": 8117
|
||||
"port": 8117,
|
||||
"blocks": 30, //amount of blocks to send at a time
|
||||
"payments": 30, //amount of payments to send at a time
|
||||
"password": "test" //password required for admin stats
|
||||
},
|
||||
|
||||
/* Coin daemon connection details. */
|
||||
|
@ -322,11 +327,11 @@ node init.js -module=api
|
|||
|
||||
#### 5) Host the front-end
|
||||
|
||||
Simply host the contents of the `website` directory on file server capable of serving simple static files.
|
||||
Simply host the contents of the `website_example` directory on file server capable of serving simple static files.
|
||||
|
||||
|
||||
In the `website` directory copy `config_example.js` to `config.js` then edit the variables in
|
||||
the file to use your pool's specific configuration. Variable explanations:
|
||||
Edit the variables in the `website_example/config.js` file to use your pool's specific configuration.
|
||||
Variable explanations:
|
||||
|
||||
```javascript
|
||||
|
||||
|
@ -351,22 +356,22 @@ var cryptonatorWidget = ["XMR-BTC", "XMR-USD", "XMR-EUR", "XMR-GBP"];
|
|||
/* Download link to cryptonote-easy-miner for Windows users. */
|
||||
var easyminerDownload = "https://github.com/zone117x/cryptonote-easy-miner/releases/";
|
||||
|
||||
/* Download link to simplewallet for your configured coin. */
|
||||
var simplewalletDownload = "http://bit.ly/monero-starter-pack";
|
||||
|
||||
/* Used for front-end block links. For other coins it can be changed, for example with
|
||||
Bytecoin you can use "https://minergate.com/blockchain/bcn/block/". */
|
||||
var blockchainExplorer = "http://monerochain.info/block/";
|
||||
|
||||
/* Used by front-end transaction links. Change for other coins. */
|
||||
var transactionExplorer = "http://monerochain.info/tx/";
|
||||
|
||||
```
|
||||
|
||||
#### 6) Customize your website
|
||||
|
||||
The following files are included so that you can customize your pool website without having to make significant changes
|
||||
to `index.html` thus reducing the difficulty of merging updates to `index.html` with your own changes:
|
||||
* `additional.css` for creating your own pool style
|
||||
* `addtional.js` for changing the functionality of your pool website
|
||||
* `info.html` for display news/updates/information on your site
|
||||
to `index.html` or other front-end files thus reducing the difficulty of merging updates with your own changes:
|
||||
* `custom.css` for creating your own pool style
|
||||
* `custom.js` for changing the functionality of your pool website
|
||||
|
||||
|
||||
Then simply serve the files via nginx, Apache, Google Drive, or anything that can host static content.
|
||||
|
||||
|
|
|
@ -35,6 +35,12 @@
|
|||
"port": 7777,
|
||||
"difficulty": 10000,
|
||||
"desc": "High end hardware"
|
||||
},
|
||||
{
|
||||
"port": 8888,
|
||||
"difficulty": 10000,
|
||||
"desc": "Hidden port",
|
||||
"hidden": true
|
||||
}
|
||||
],
|
||||
"varDiff": {
|
||||
|
@ -64,6 +70,7 @@
|
|||
"enabled": true,
|
||||
"interval": 600,
|
||||
"maxAddresses": 50,
|
||||
"mixin": 3,
|
||||
"transferFee": 5000000000,
|
||||
"minPayment": 100000000000,
|
||||
"denomination": 100000000000
|
||||
|
@ -74,7 +81,8 @@
|
|||
"interval": 30,
|
||||
"depth": 60,
|
||||
"poolFee": 2,
|
||||
"devDonation": 0.1
|
||||
"devDonation": 0.1,
|
||||
"coreDevDonation": 0.1
|
||||
},
|
||||
|
||||
"api": {
|
||||
|
@ -82,6 +90,8 @@
|
|||
"hashrateWindow": 600,
|
||||
"updateInterval": 5,
|
||||
"port": 8117,
|
||||
"blocks": 30,
|
||||
"payments": 30,
|
||||
"password": "your_password"
|
||||
},
|
||||
|
||||
|
|
19
init.js
19
init.js
|
@ -5,24 +5,7 @@ var os = require('os');
|
|||
var redis = require('redis');
|
||||
|
||||
|
||||
var configFile = (function(){
|
||||
for (var i = 0; i < process.argv.length; i++){
|
||||
if (process.argv[i].indexOf('-config=') === 0)
|
||||
return process.argv[i].split('=')[1];
|
||||
}
|
||||
return 'config.json';
|
||||
})();
|
||||
|
||||
|
||||
try {
|
||||
global.config = JSON.parse(fs.readFileSync(configFile));
|
||||
}
|
||||
catch(e){
|
||||
console.error('Failed to read config file ' + configFile + '\n\n' + e);
|
||||
return;
|
||||
}
|
||||
|
||||
config.version = "v0.99.0.6";
|
||||
require('./lib/configReader.js');
|
||||
|
||||
require('./lib/logger.js');
|
||||
|
||||
|
|
209
lib/api.js
209
lib/api.js
|
@ -4,7 +4,6 @@ var url = require("url");
|
|||
var zlib = require('zlib');
|
||||
|
||||
var async = require('async');
|
||||
var redis = require('redis');
|
||||
|
||||
var apiInterfaces = require('./apiInterfaces.js')(config.daemon, config.wallet);
|
||||
|
||||
|
@ -13,13 +12,16 @@ require('./exceptionWriter.js')(logSystem);
|
|||
|
||||
var redisCommands = [
|
||||
['zremrangebyscore', config.coin + ':hashrate', '-inf', ''],
|
||||
['zrangebyscore', config.coin + ':hashrate', '', '+inf'],
|
||||
['hgetall', config.coin + ':stats'],
|
||||
['smembers', config.coin + ':blocksPending'],
|
||||
['smembers', config.coin + ':blocksUnlocked'],
|
||||
['smembers', config.coin + ':blocksOrphaned'],
|
||||
['zrange', config.coin + ':hashrate', 0, -1],
|
||||
['hgetall', config.coin + ':stats'],
|
||||
['zrange', config.coin + ':blocks:candidates', 0, -1, 'WITHSCORES'],
|
||||
['zrevrange', config.coin + ':blocks:matured', 0, config.api.blocks - 1, 'WITHSCORES'],
|
||||
['hgetall', config.coin + ':shares:roundCurrent'],
|
||||
['hgetall', config.coin + ':stats']
|
||||
['hgetall', config.coin + ':stats'],
|
||||
['zcard', config.coin + ':blocks:matured'],
|
||||
['zrevrange', config.coin + ':payments:all', 0, config.api.payments - 1, 'WITHSCORES'],
|
||||
['zcard', config.coin + ':payments:all'],
|
||||
['keys', config.coin + ':payments:*']
|
||||
];
|
||||
|
||||
var currentStats = "";
|
||||
|
@ -40,7 +42,6 @@ function collectStats(){
|
|||
|
||||
var windowTime = (((Date.now() / 1000) - config.api.hashrateWindow) | 0).toString();
|
||||
redisCommands[0][3] = '(' + windowTime;
|
||||
redisCommands[1][2] = windowTime;
|
||||
|
||||
async.parallel({
|
||||
pool: function(callback){
|
||||
|
@ -56,11 +57,11 @@ function collectStats(){
|
|||
|
||||
var data = {
|
||||
stats: replies[2],
|
||||
blocks: {
|
||||
pending: replies[3],
|
||||
unlocked: replies[4],
|
||||
orphaned: replies[5]
|
||||
}
|
||||
blocks: replies[3].concat(replies[4]),
|
||||
totalBlocks: parseInt(replies[7]) + (replies[3].length / 2),
|
||||
payments: replies[8],
|
||||
totalPayments: parseInt(replies[9]),
|
||||
totalMinersPaid: replies[10].length - 1
|
||||
};
|
||||
|
||||
var hashrates = replies[1];
|
||||
|
@ -86,14 +87,14 @@ function collectStats(){
|
|||
|
||||
data.roundHashes = 0;
|
||||
|
||||
if (replies[6]){
|
||||
for (var miner in replies[6]){
|
||||
data.roundHashes += parseInt(replies[6][miner]);
|
||||
if (replies[5]){
|
||||
for (var miner in replies[5]){
|
||||
data.roundHashes += parseInt(replies[5][miner]);
|
||||
}
|
||||
}
|
||||
|
||||
if (replies[7]) {
|
||||
data.lastBlockFound = replies[7].lastBlockFound;
|
||||
if (replies[6]) {
|
||||
data.lastBlockFound = replies[6].lastBlockFound;
|
||||
}
|
||||
|
||||
callback(null, data);
|
||||
|
@ -119,14 +120,18 @@ function collectStats(){
|
|||
},
|
||||
config: function(callback){
|
||||
callback(null, {
|
||||
ports: config.poolServer.ports,
|
||||
ports: getPublicPorts(config.poolServer.ports),
|
||||
hashrateWindow: config.api.hashrateWindow,
|
||||
fee: config.blockUnlocker.poolFee,
|
||||
coin: config.coin,
|
||||
symbol: config.symbol,
|
||||
depth: config.blockUnlocker.depth,
|
||||
version: config.version,
|
||||
donation: config.blockUnlocker.devDonation
|
||||
donation: config.blockUnlocker.devDonation,
|
||||
coreDonation: config.blockUnlocker.coreDevDonation,
|
||||
doDonations: doDonations,
|
||||
version: version,
|
||||
minPaymentThreshold: config.payments.minPayment,
|
||||
denominationUnit: config.payments.denomination
|
||||
});
|
||||
}
|
||||
}, function(error, results){
|
||||
|
@ -150,6 +155,12 @@ function collectStats(){
|
|||
|
||||
}
|
||||
|
||||
function getPublicPorts(ports){
|
||||
return ports.filter(function(port) {
|
||||
return !port.hidden;
|
||||
});
|
||||
}
|
||||
|
||||
function getReadableHashRateString(hashrate){
|
||||
var i = 0;
|
||||
var byteUnits = [' H', ' KH', ' MH', ' GH', ' TH', ' PH' ];
|
||||
|
@ -172,16 +183,23 @@ function broadcastLiveStats(){
|
|||
var redisCommands = [];
|
||||
for (var address in addressConnections){
|
||||
redisCommands.push(['hgetall', config.coin + ':workers:' + address]);
|
||||
redisCommands.push(['zrevrange', config.coin + ':payments:' + address, 0, config.api.payments - 1, 'WITHSCORES']);
|
||||
}
|
||||
redisClient.multi(redisCommands).exec(function(error, replies){
|
||||
|
||||
var addresses = Object.keys(addressConnections);
|
||||
|
||||
for (var i = 0; i < addresses.length; i++){
|
||||
var offset = i * 2;
|
||||
var address = addresses[i];
|
||||
var stats = replies[i];
|
||||
var stats = replies[offset];
|
||||
var res = addressConnections[address];
|
||||
res.end(stats ? formatMinerStats(stats, address) : '{"error": "not found"');
|
||||
if (!stats){
|
||||
res.end(JSON.stringify({error: "not found"}));
|
||||
return;
|
||||
}
|
||||
stats.hashrate = minerStats[address];
|
||||
res.end(JSON.stringify({stats: stats, payments: replies[offset + 1]}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -209,21 +227,84 @@ function handleMinerStats(urlParts, response){
|
|||
});
|
||||
}
|
||||
else{
|
||||
redisClient.hgetall(config.coin + ':workers:' + address, function(error, stats){
|
||||
if (!stats){
|
||||
redisClient.multi([
|
||||
['hgetall', config.coin + ':workers:' + address],
|
||||
['zrevrange', config.coin + ':payments:' + address, 0, config.api.payments - 1, 'WITHSCORES']
|
||||
]).exec(function(error, replies){
|
||||
if (error || !replies[0]){
|
||||
response.end(JSON.stringify({error: 'not found'}));
|
||||
return;
|
||||
}
|
||||
response.end(formatMinerStats(stats, address));
|
||||
var stats = replies[0];
|
||||
stats.hashrate = minerStats[address];
|
||||
response.end(JSON.stringify({stats: stats, payments: replies[1]}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function formatMinerStats(redisData, address){
|
||||
redisData.hashrate = minerStats[address];
|
||||
redisData.symbol = config.symbol;
|
||||
return JSON.stringify({stats: redisData});
|
||||
function handleGetPayments(urlParts, response){
|
||||
var paymentKey = ':payments:all';
|
||||
|
||||
if (urlParts.query.address)
|
||||
paymentKey = ':payments:' + urlParts.query.address;
|
||||
|
||||
redisClient.zrevrangebyscore(
|
||||
config.coin + paymentKey,
|
||||
'(' + urlParts.query.time,
|
||||
'-inf',
|
||||
'WITHSCORES',
|
||||
'LIMIT',
|
||||
0,
|
||||
config.api.payments,
|
||||
function(err, result){
|
||||
|
||||
var reply;
|
||||
|
||||
if (err)
|
||||
reply = JSON.stringify({error: 'query failed'});
|
||||
else
|
||||
reply = JSON.stringify(result);
|
||||
|
||||
response.writeHead("200", {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': reply.length
|
||||
});
|
||||
response.end(reply);
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function handleGetBlocks(urlParts, response){
|
||||
redisClient.zrevrangebyscore(
|
||||
config.coin + ':blocks:matured',
|
||||
'(' + urlParts.query.height,
|
||||
'-inf',
|
||||
'WITHSCORES',
|
||||
'LIMIT',
|
||||
0,
|
||||
config.api.blocks,
|
||||
function(err, result){
|
||||
|
||||
var reply;
|
||||
|
||||
if (err)
|
||||
reply = JSON.stringify({error: 'query failed'});
|
||||
else
|
||||
reply = JSON.stringify(result);
|
||||
|
||||
response.writeHead("200", {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': reply.length
|
||||
});
|
||||
response.end(reply);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -252,21 +333,24 @@ function handleAdminStats(response){
|
|||
|
||||
async.waterfall([
|
||||
|
||||
//Get worker keys
|
||||
//Get worker keys & unlocked blocks
|
||||
function(callback){
|
||||
redisClient.keys(config.coin + ':workers:*', function(error, result) {
|
||||
redisClient.multi([
|
||||
['keys', config.coin + ':workers:*'],
|
||||
['zrange', config.coin + ':blocks:matured', 0, -1]
|
||||
]).exec(function(error, replies) {
|
||||
if (error) {
|
||||
log('error', logSystem, 'Error trying to get worker balances from redis %j', [error]);
|
||||
log('error', logSystem, 'Error trying to get admin data from redis %j', [error]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
callback(null, result);
|
||||
callback(null, replies[0], replies[1]);
|
||||
});
|
||||
},
|
||||
|
||||
//Get worker balances
|
||||
function(keys, callback){
|
||||
var redisCommands = keys.map(function(k){
|
||||
function(workerKeys, blocks, callback){
|
||||
var redisCommands = workerKeys.map(function(k){
|
||||
return ['hmget', k, 'balance', 'paid'];
|
||||
});
|
||||
redisClient.multi(redisCommands).exec(function(error, replies){
|
||||
|
@ -276,19 +360,42 @@ function handleAdminStats(response){
|
|||
return;
|
||||
}
|
||||
|
||||
var stats = {
|
||||
totalOwed: 0,
|
||||
totalPaid: 0
|
||||
};
|
||||
|
||||
for (var i = 0; i < replies.length; i++){
|
||||
stats.totalOwed += parseInt(replies[i][0]) || 0;
|
||||
stats.totalPaid += parseInt(replies[i][1]) || 0;
|
||||
}
|
||||
|
||||
callback(null, stats);
|
||||
callback(null, replies, blocks);
|
||||
});
|
||||
}], function(error, stats){
|
||||
},
|
||||
function(workerData, blocks, callback){
|
||||
var stats = {
|
||||
totalOwed: 0,
|
||||
totalPaid: 0,
|
||||
totalRevenue: 0,
|
||||
totalDiff: 0,
|
||||
totalShares: 0,
|
||||
blocksOrphaned: 0,
|
||||
blocksUnlocked: 0,
|
||||
totalWorkers: 0
|
||||
};
|
||||
|
||||
for (var i = 0; i < workerData.length; i++){
|
||||
stats.totalOwed += parseInt(workerData[i][0]) || 0;
|
||||
stats.totalPaid += parseInt(workerData[i][1]) || 0;
|
||||
stats.totalWorkers++;
|
||||
}
|
||||
|
||||
for (var i = 0; i < blocks.length; i++){
|
||||
var block = blocks[i].split(':');
|
||||
if (block[5]) {
|
||||
stats.blocksUnlocked++;
|
||||
stats.totalDiff += parseInt(block[2]);
|
||||
stats.totalShares += parseInt(block[3]);
|
||||
stats.totalRevenue += parseInt(block[5]);
|
||||
}
|
||||
else{
|
||||
stats.blocksOrphaned++;
|
||||
}
|
||||
}
|
||||
callback(null, stats);
|
||||
}
|
||||
], function(error, stats){
|
||||
if (error){
|
||||
response.end(JSON.stringify({error: 'error collecting stats'}));
|
||||
return;
|
||||
|
@ -346,6 +453,12 @@ var server = http.createServer(function(request, response){
|
|||
case '/stats_address':
|
||||
handleMinerStats(urlParts, response);
|
||||
break;
|
||||
case '/get_payments':
|
||||
handleGetPayments(urlParts, response);
|
||||
break;
|
||||
case '/get_blocks':
|
||||
handleGetBlocks(urlParts, response);
|
||||
break;
|
||||
case '/admin_stats':
|
||||
if (!authorize(request, response))
|
||||
return;
|
||||
|
|
|
@ -24,36 +24,37 @@ apiInterfaces.batchRpcDaemon(batchArray, function(error, response){
|
|||
*/
|
||||
|
||||
|
||||
var devDonationAddress = '45Jmf8PnJKziGyrLouJMeBFw2yVyX1QB52sKEQ4S1VSU2NVsaVGPNu4bWKkaHaeZ6tWCepP6iceZk8XhTLzDaEVa72QrtVh';
|
||||
|
||||
var doDonations = config.blockUnlocker.devDonation > 0 && devDonationAddress[0] === config.poolServer.poolAddress[0];
|
||||
|
||||
|
||||
function runInterval(){
|
||||
async.waterfall([
|
||||
|
||||
//Get all pending blocks in redis
|
||||
//Get all block candidates in redis
|
||||
function(callback){
|
||||
redisClient.smembers(config.coin + ':blocksPending', function(error, result){
|
||||
redisClient.zrange(config.coin + ':blocks:candidates', 0, -1, 'WITHSCORES', function(error, results){
|
||||
if (error){
|
||||
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
if (result.length === 0){
|
||||
log('info', logSystem, 'No pending blocks in redis');
|
||||
if (results.length === 0){
|
||||
log('info', logSystem, 'No blocks candidates in redis');
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
var blocks = result.map(function(item){
|
||||
var parts = item.split(':');
|
||||
return {
|
||||
height: parseInt(parts[0]),
|
||||
difficulty: parseInt(parts[1]),
|
||||
hash: parts[2],
|
||||
serialized: item
|
||||
};
|
||||
});
|
||||
|
||||
var blocks = [];
|
||||
|
||||
for (var i = 0; i < results.length; i += 2){
|
||||
var parts = results[i].split(':');
|
||||
blocks.push({
|
||||
serialized: results[i],
|
||||
height: parseInt(results[i + 1]),
|
||||
hash: parts[0],
|
||||
time: parts[1],
|
||||
difficulty: parts[2],
|
||||
shares: parts[3]
|
||||
});
|
||||
}
|
||||
|
||||
callback(null, blocks);
|
||||
});
|
||||
},
|
||||
|
@ -75,7 +76,7 @@ function runInterval(){
|
|||
return;
|
||||
}
|
||||
var blockHeader = result.block_header;
|
||||
block.orphan = (blockHeader.hash !== block.hash);
|
||||
block.orphaned = blockHeader.hash === block.hash ? 0 : 1;
|
||||
block.unlocked = blockHeader.depth >= config.blockUnlocker.depth;
|
||||
block.reward = blockHeader.reward;
|
||||
mapCback(block.unlocked);
|
||||
|
@ -83,7 +84,7 @@ function runInterval(){
|
|||
}, function(unlockedBlocks){
|
||||
|
||||
if (unlockedBlocks.length === 0){
|
||||
log('info', logSystem, 'No pending blocks are unlocked or orphaned yet (%d pending)', [blocks.length]);
|
||||
log('info', logSystem, 'No pending blocks are unlocked yet (%d pending)', [blocks.length]);
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
@ -110,9 +111,6 @@ function runInterval(){
|
|||
for (var i = 0; i < replies.length; i++){
|
||||
var workerShares = replies[i];
|
||||
blocks[i].workerShares = workerShares;
|
||||
blocks[i].totalShares = Object.keys(workerShares).reduce(function(p, c){
|
||||
return p + parseInt(workerShares[c])
|
||||
}, 0);
|
||||
}
|
||||
callback(null, blocks);
|
||||
});
|
||||
|
@ -121,19 +119,29 @@ function runInterval(){
|
|||
//Handle orphaned blocks
|
||||
function(blocks, callback){
|
||||
var orphanCommands = [];
|
||||
|
||||
blocks.forEach(function(block){
|
||||
if (!block.orphan) return;
|
||||
var workerShares = block.workerShares;
|
||||
if (!block.orphaned) return;
|
||||
|
||||
orphanCommands.push(['del', config.coin + ':shares:round' + block.height]);
|
||||
|
||||
orphanCommands.push(['srem', config.coin + ':blocksPending', block.serialized]);
|
||||
orphanCommands.push(['sadd', config.coin + ':blocksOrphaned', block.serialized + ':' + block.totalShares]);
|
||||
orphanCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
|
||||
orphanCommands.push(['zadd', config.coin + ':blocks:matured', block.height, [
|
||||
block.hash,
|
||||
block.time,
|
||||
block.difficulty,
|
||||
block.shares,
|
||||
block.orphaned
|
||||
].join(':')]);
|
||||
|
||||
Object.keys(workerShares).forEach(function(worker){
|
||||
orphanCommands.push(['hincrby', config.coin + ':shares:roundCurrent',
|
||||
worker, workerShares[worker]]);
|
||||
});
|
||||
if (block.workerShares) {
|
||||
var workerShares = block.workerShares;
|
||||
Object.keys(workerShares).forEach(function (worker) {
|
||||
orphanCommands.push(['hincrby', config.coin + ':shares:roundCurrent', worker, workerShares[worker]]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (orphanCommands.length > 0){
|
||||
redisClient.multi(orphanCommands).exec(function(error, replies){
|
||||
if (error){
|
||||
|
@ -155,30 +163,43 @@ function runInterval(){
|
|||
var payments = {};
|
||||
var totalBlocksUnlocked = 0;
|
||||
blocks.forEach(function(block){
|
||||
if (block.orphan) return;
|
||||
if (block.orphaned) return;
|
||||
totalBlocksUnlocked++;
|
||||
|
||||
unlockedBlocksCommands.push(['del', config.coin + ':shares:round' + block.height]);
|
||||
|
||||
unlockedBlocksCommands.push(['srem', config.coin + ':blocksPending', block.serialized]);
|
||||
unlockedBlocksCommands.push(['sadd', config.coin + ':blocksUnlocked', block.serialized + ':' + block.totalShares]);
|
||||
unlockedBlocksCommands.push(['zrem', config.coin + ':blocks:candidates', block.serialized]);
|
||||
unlockedBlocksCommands.push(['zadd', config.coin + ':blocks:matured', block.height, [
|
||||
block.hash,
|
||||
block.time,
|
||||
block.difficulty,
|
||||
block.shares,
|
||||
block.orphaned,
|
||||
block.reward
|
||||
].join(':')]);
|
||||
|
||||
var feePercent = config.blockUnlocker.poolFee / 100;
|
||||
|
||||
if (doDonations) {
|
||||
feePercent += config.blockUnlocker.devDonation / 100;
|
||||
feePercent += config.blockUnlocker.coreDevDonation / 100;
|
||||
|
||||
var devDonation = block.reward * (config.blockUnlocker.devDonation / 100);
|
||||
payments[devDonationAddress] = devDonation;
|
||||
|
||||
var coreDevDonation = block.reward * (config.blockUnlocker.coreDevDonation / 100);
|
||||
payments[coreDevDonationAddress] = coreDevDonation;
|
||||
}
|
||||
|
||||
var reward = block.reward - (block.reward * feePercent);
|
||||
|
||||
var totalShares = block.totalShares;
|
||||
Object.keys(block.workerShares).forEach(function(worker){
|
||||
var percent = block.workerShares[worker] / totalShares;
|
||||
var workerReward = reward * percent;
|
||||
payments[worker] = (payments[worker] || 0) + workerReward;
|
||||
});
|
||||
if (block.workerShares) {
|
||||
var totalShares = parseInt(block.shares);
|
||||
Object.keys(block.workerShares).forEach(function (worker) {
|
||||
var percent = block.workerShares[worker] / totalShares;
|
||||
var workerReward = reward * percent;
|
||||
payments[worker] = (payments[worker] || 0) + workerReward;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (var worker in payments) {
|
||||
|
@ -205,9 +226,7 @@ function runInterval(){
|
|||
log('info', logSystem, 'Unlocked %d blocks and update balances for %d workers', [totalBlocksUnlocked, Object.keys(payments).length]);
|
||||
callback(null);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
], function(error, result){
|
||||
setTimeout(runInterval, config.blockUnlocker.interval * 1000);
|
||||
})
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
var fs = require('fs');
|
||||
|
||||
var configFile = (function(){
|
||||
for (var i = 0; i < process.argv.length; i++){
|
||||
if (process.argv[i].indexOf('-config=') === 0)
|
||||
return process.argv[i].split('=')[1];
|
||||
}
|
||||
return 'config.json';
|
||||
})();
|
||||
|
||||
|
||||
try {
|
||||
global.config = JSON.parse(fs.readFileSync(configFile));
|
||||
}
|
||||
catch(e){
|
||||
console.error('Failed to read config file ' + configFile + '\n\n' + e);
|
||||
return;
|
||||
}
|
||||
|
||||
global.version = "v0.99.3.1";
|
||||
global.devDonationAddress = '45Jmf8PnJKziGyrLouJMeBFw2yVyX1QB52sKEQ4S1VSU2NVsaVGPNu4bWKkaHaeZ6tWCepP6iceZk8XhTLzDaEVa72QrtVh';
|
||||
global.coreDevDonationAddress = '46BeWrHpwXmHDpDEUmZBWZfoQpdc6HaERCNmx1pEYL2rAcuwufPN9rXHHtyUA4QVy66qeFQkn6sfK8aHYjA3jk3o1Bv16em';
|
||||
global.doDonations = devDonationAddress[0] === config.poolServer.poolAddress[0] && (
|
||||
config.blockUnlocker.devDonation > 0 || config.blockUnlocker.coreDevDonation > 0
|
||||
);
|
|
@ -77,10 +77,11 @@ function runInterval(){
|
|||
for (var i = 0; i < transferCommandsLength; i++){
|
||||
transferCommands.push({
|
||||
redis: [],
|
||||
amount : 0,
|
||||
rpc: {
|
||||
destinations: [],
|
||||
fee: config.payments.transferFee,
|
||||
mixin: 0,
|
||||
mixin: config.payments.mixin,
|
||||
unlock_time: 0
|
||||
}
|
||||
});
|
||||
|
@ -94,6 +95,7 @@ function runInterval(){
|
|||
transferCommands[commandIndex].rpc.destinations.push({amount: amount, address: worker});
|
||||
transferCommands[commandIndex].redis.push(['hincrby', config.coin + ':workers:' + worker, 'balance', -amount]);
|
||||
transferCommands[commandIndex].redis.push(['hincrby', config.coin + ':workers:' + worker, 'paid', amount]);
|
||||
transferCommands[commandIndex].amount += amount;
|
||||
|
||||
addresses++;
|
||||
if (addresses >= config.payments.maxAddresses){
|
||||
|
@ -102,6 +104,8 @@ function runInterval(){
|
|||
}
|
||||
}
|
||||
|
||||
var timeOffset = 0;
|
||||
|
||||
async.filter(transferCommands, function(transferCmd, cback){
|
||||
apiInterfaces.rpcWallet('transfer', transferCmd.rpc, function(error, result){
|
||||
if (error){
|
||||
|
@ -110,6 +114,31 @@ function runInterval(){
|
|||
cback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var now = (timeOffset++) + Date.now() / 1000 | 0;
|
||||
var txHash = result.tx_hash.replace('<', '').replace('>', '');
|
||||
|
||||
|
||||
transferCmd.redis.push(['zadd', config.coin + ':payments:all', now, [
|
||||
txHash,
|
||||
transferCmd.amount,
|
||||
transferCmd.rpc.fee,
|
||||
transferCmd.rpc.mixin,
|
||||
Object.keys(transferCmd.rpc.destinations).length
|
||||
].join(':')]);
|
||||
|
||||
|
||||
for (var i = 0; i < transferCmd.rpc.destinations.length; i++){
|
||||
var destination = transferCmd.rpc.destinations[i];
|
||||
transferCmd.redis.push(['zadd', config.coin + ':payments:' + destination.address, now, [
|
||||
txHash,
|
||||
destination.amount,
|
||||
transferCmd.rpc.fee,
|
||||
transferCmd.rpc.mixin
|
||||
].join(':')]);
|
||||
}
|
||||
|
||||
|
||||
log('info', logSystem, 'Payments sent via wallet daemon %j', [result]);
|
||||
redisClient.multi(transferCmd.redis).exec(function(error, replies){
|
||||
if (error){
|
||||
|
|
38
lib/pool.js
38
lib/pool.js
|
@ -154,7 +154,7 @@ function processBlockTemplate(template){
|
|||
if (currentBlockTemplate)
|
||||
validBlockTemplates.push(currentBlockTemplate);
|
||||
|
||||
if (validBlockTemplates.length > 10)
|
||||
if (validBlockTemplates.length > 3)
|
||||
validBlockTemplates.shift();
|
||||
|
||||
currentBlockTemplate = new BlockTemplate(template);
|
||||
|
@ -335,7 +335,7 @@ Miner.prototype = {
|
|||
|
||||
|
||||
|
||||
function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareType){
|
||||
function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareType, blockTemplate){
|
||||
|
||||
var dateNow = Date.now();
|
||||
var dateNowSeconds = dateNow / 1000 | 0;
|
||||
|
@ -348,15 +348,33 @@ function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareTy
|
|||
];
|
||||
|
||||
if (blockCandidate){
|
||||
redisCommands.push(['sadd', config.coin + ':blocksPending', [job.height, currentBlockTemplate.difficulty, hashHex, Date.now() / 1000 | 0].join(':')]);
|
||||
redisCommands.push(['rename', config.coin + ':shares:roundCurrent', config.coin + ':shares:round' + job.height]);
|
||||
redisCommands.push(['hset', config.coin + ':stats', 'lastBlockFound', Date.now()]);
|
||||
redisCommands.push(['rename', config.coin + ':shares:roundCurrent', config.coin + ':shares:round' + job.height]);
|
||||
redisCommands.push(['hgetall', config.coin + ':shares:round' + job.height]);
|
||||
}
|
||||
|
||||
redisClient.multi(redisCommands).exec(function(err, replies){
|
||||
if (err){
|
||||
log('error', logSystem, 'Failed to insert share data into redis %j', [err]);
|
||||
log('error', logSystem, 'Failed to insert share data into redis %j \n %j', [err, redisCommands]);
|
||||
return;
|
||||
}
|
||||
if (blockCandidate){
|
||||
var workerShares = replies[replies.length - 1];
|
||||
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
||||
return p + parseInt(workerShares[c])
|
||||
}, 0);
|
||||
redisClient.zadd(config.coin + ':blocks:candidates', job.height, [
|
||||
hashHex,
|
||||
Date.now() / 1000 | 0,
|
||||
blockTemplate.difficulty,
|
||||
totalShares
|
||||
].join(':'), function(err, result){
|
||||
if (err){
|
||||
log('error', logSystem, 'Failed inserting block candidate %s \n %j', [hashHex, err]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
log('info', logSystem, 'Accepted %s share at difficulty %d/%d from %s@%s', [shareType, job.difficulty, shareDiff, miner.login, miner.ip]);
|
||||
|
@ -385,7 +403,7 @@ function processShare(miner, job, blockTemplate, nonce, resultHash){
|
|||
|
||||
|
||||
if (hash.toString('hex') !== resultHash) {
|
||||
log('error', logSystem, 'Bad hash from miner %s@%s', [miner.login, miner.ip]);
|
||||
log('warn', logSystem, 'Bad hash from miner %s@%s', [miner.login, miner.ip]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -394,22 +412,22 @@ function processShare(miner, job, blockTemplate, nonce, resultHash){
|
|||
var hashNum = bignum.fromBuffer(new Buffer(hashArray));
|
||||
var hashDiff = diff1.div(hashNum);
|
||||
|
||||
var blockFastHash;
|
||||
|
||||
|
||||
if (hashDiff.ge(blockTemplate.difficulty)){
|
||||
blockFastHash = cnUtil.get_block_id(shareBuffer).toString('hex');
|
||||
|
||||
apiInterfaces.rpcDaemon('submitblock', [shareBuffer.toString('hex')], function(error, result){
|
||||
if (error){
|
||||
log('error', logSystem, 'Error submitting block at height %d - %j', [job.height, error]);
|
||||
log('error', logSystem, 'Error submitting block at height %d from %s@%s, share type: "%s" - %j', [job.height, miner.login, miner.ip, shareType, error]);
|
||||
recordShareData(miner, job, hashDiff.toString(), false, null, shareType);
|
||||
}
|
||||
else{
|
||||
var blockFastHash = cnUtil.get_block_id(shareBuffer).toString('hex');
|
||||
log('info', logSystem,
|
||||
'Block %s found at height %d by miner %s@%s - submit result: %j',
|
||||
[blockFastHash.substr(0, 6), job.height, miner.login, miner.ip, result]
|
||||
);
|
||||
recordShareData(miner, job, hashDiff.toString(), true, blockFastHash, shareType);
|
||||
recordShareData(miner, job, hashDiff.toString(), true, blockFastHash, shareType, blockTemplate);
|
||||
jobRefresh();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
|
||||
|
||||
/*
|
||||
|
||||
This script converts the block data in redis from the old format (v0.99.0.6 and earlier) to the new format
|
||||
used in v0.99.1+
|
||||
|
||||
*/
|
||||
|
||||
var util = require('util');
|
||||
|
||||
var async = require('async');
|
||||
|
||||
var redis = require('redis');
|
||||
|
||||
require('./lib/configReader.js');
|
||||
|
||||
var apiInterfaces = require('./lib/apiInterfaces.js')(config.daemon, config.wallet);
|
||||
|
||||
|
||||
function log(severity, system, text, data){
|
||||
|
||||
var formattedMessage = text;
|
||||
|
||||
if (data) {
|
||||
data.unshift(text);
|
||||
formattedMessage = util.format.apply(null, data);
|
||||
}
|
||||
|
||||
console.log(severity + ': ' + formattedMessage);
|
||||
}
|
||||
|
||||
|
||||
var logSystem = 'reward script';
|
||||
|
||||
var redisClient = redis.createClient(config.redis.port, config.redis.host);
|
||||
|
||||
function getTotalShares(height, callback){
|
||||
|
||||
redisClient.hgetall(config.coin + ':shares:round' + height, function(err, workerShares){
|
||||
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
||||
return p + parseInt(workerShares[c])
|
||||
}, 0);
|
||||
|
||||
callback(null, totalShares);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async.series([
|
||||
function(callback){
|
||||
redisClient.smembers(config.coin + ':blocksUnlocked', function(error, result){
|
||||
if (error){
|
||||
log('error', logSystem, 'Error trying to get unlocke blocks from redis %j', [error]);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (result.length === 0){
|
||||
log('info', logSystem, 'No unlocked blocks in redis');
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var blocks = result.map(function(item){
|
||||
var parts = item.split(':');
|
||||
return {
|
||||
height: parseInt(parts[0]),
|
||||
difficulty: parts[1],
|
||||
hash: parts[2],
|
||||
time: parts[3],
|
||||
shares: parts[4],
|
||||
orphaned: 0
|
||||
};
|
||||
});
|
||||
|
||||
async.map(blocks, function(block, mapCback){
|
||||
apiInterfaces.rpcDaemon('getblockheaderbyheight', {height: block.height}, function(error, result){
|
||||
if (error){
|
||||
log('error', logSystem, 'Error with getblockheaderbyheight RPC request for block %s - %j', [block.serialized, error]);
|
||||
mapCback(null, block);
|
||||
return;
|
||||
}
|
||||
if (!result.block_header){
|
||||
log('error', logSystem, 'Error with getblockheaderbyheight, no details returned for %s - %j', [block.serialized, result]);
|
||||
mapCback(null, block);
|
||||
return;
|
||||
}
|
||||
var blockHeader = result.block_header;
|
||||
block.reward = blockHeader.reward;
|
||||
mapCback(null, block);
|
||||
});
|
||||
}, function(err, blocks){
|
||||
|
||||
if (blocks.length === 0){
|
||||
log('info', logSystem, 'No unlocked blocks');
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var zaddCommands = [config.coin + ':blocks:matured'];
|
||||
|
||||
for (var i = 0; i < blocks.length; i++){
|
||||
var block = blocks[i];
|
||||
zaddCommands.push(block.height);
|
||||
zaddCommands.push([
|
||||
block.hash,
|
||||
block.time,
|
||||
block.difficulty,
|
||||
block.shares,
|
||||
block.orphaned,
|
||||
block.reward
|
||||
].join(':'));
|
||||
}
|
||||
|
||||
redisClient.zadd(zaddCommands, function(err, result){
|
||||
if (err){
|
||||
console.log('failed zadd ' + JSON.stringify(err));
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
console.log('successfully converted unlocked blocks to matured blocks');
|
||||
callback();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
},
|
||||
function(callback){
|
||||
redisClient.smembers(config.coin + ':blocksPending', function(error, result) {
|
||||
if (error) {
|
||||
log('error', logSystem, 'Error trying to get pending blocks from redis %j', [error]);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (result.length === 0) {
|
||||
log('info', logSystem, 'No pending blocks in redis');
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
async.map(result, function(item, mapCback){
|
||||
var parts = item.split(':');
|
||||
var block = {
|
||||
height: parseInt(parts[0]),
|
||||
difficulty: parts[1],
|
||||
hash: parts[2],
|
||||
time: parts[3],
|
||||
serialized: item
|
||||
};
|
||||
getTotalShares(block.height, function(err, shares){
|
||||
block.shares = shares;
|
||||
mapCback(null, block);
|
||||
});
|
||||
}, function(err, blocks){
|
||||
|
||||
var zaddCommands = [config.coin + ':blocks:candidates'];
|
||||
|
||||
for (var i = 0; i < blocks.length; i++){
|
||||
var block = blocks[i];
|
||||
zaddCommands.push(block.height);
|
||||
zaddCommands.push([
|
||||
block.hash,
|
||||
block.time,
|
||||
block.difficulty,
|
||||
block.shares
|
||||
].join(':'));
|
||||
}
|
||||
|
||||
redisClient.zadd(zaddCommands, function(err, result){
|
||||
if (err){
|
||||
console.log('failed zadd ' + JSON.stringify(err));
|
||||
return;
|
||||
}
|
||||
console.log('successfully converted pending blocks to block candidates');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
], function(){
|
||||
process.exit();
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
<script src="config.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var coinDecimals = coinUnits.toString().length - 1;
|
||||
|
||||
function getReadableCoins(coins){
|
||||
return (parseInt(coins || 0) / coinUnits).toFixed(coinDecimals);
|
||||
}
|
||||
|
||||
$(function(){
|
||||
|
||||
var password = prompt('Admin password:');
|
||||
|
||||
$.get(api + '/admin_stats', {password: password}, function(data){
|
||||
renderData(data);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function renderData(data){
|
||||
$('#totalOwed').text(getReadableCoins(data.totalOwed));
|
||||
$('#totalPaid').text(getReadableCoins(data.totalPaid));
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div>
|
||||
<span>Total Owed: </span><span id="totalOwed"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Total Paid: </span><span id="totalPaid"></span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,782 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
|
||||
|
||||
<title>Cryptonote Mining Pool</title>
|
||||
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
|
||||
|
||||
<script src="config.js"></script>
|
||||
<script src="additional.js"></script>
|
||||
<link href="additional.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
#coinName{
|
||||
text-transform: capitalize;
|
||||
}
|
||||
body {
|
||||
padding-top: 90px;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
.container{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.page{
|
||||
display: none;
|
||||
}
|
||||
.stats {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.stats:last-child{
|
||||
width: auto;
|
||||
}
|
||||
.stats > h3 > i{
|
||||
font-size: 0.80em;
|
||||
width: 21px;
|
||||
}
|
||||
.stats > div{
|
||||
padding: 5px 0;
|
||||
}
|
||||
.stats > div > .fa {
|
||||
width: 25px;
|
||||
}
|
||||
.stats > div > span:first-of-type{
|
||||
font-weight: bold;
|
||||
}
|
||||
.marketFooter{
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
#networkLastReward{
|
||||
text-transform: uppercase;
|
||||
}
|
||||
#donationPercent{
|
||||
font-size: 0.8em;
|
||||
margin-left: 5px;
|
||||
}
|
||||
#lastHash{
|
||||
font-family: monospace;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
#yourStatsInput{
|
||||
width: 820px;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
font-family: monospace;
|
||||
}
|
||||
#yourAddressDisplay > span {
|
||||
font-family: monospace;
|
||||
}
|
||||
.yourStats{
|
||||
display: none;
|
||||
}
|
||||
#yourAddressDisplay{
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
font-family: monospace;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
#addressError{
|
||||
color: red;
|
||||
}
|
||||
#stats_updated{
|
||||
opacity: 0;
|
||||
float: right;
|
||||
margin-left: 30px;
|
||||
color: #e8e8e8;
|
||||
line-height: 47px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.blocksStatHolder > h4{
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.blocksStatHolder > span{
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
padding: 1px 9px;
|
||||
border: 1px solid #e5e5e5;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
|
||||
#blocks_rows > tr > td{
|
||||
vertical-align: middle;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.luckGood{
|
||||
color: darkgreen;
|
||||
}
|
||||
.luckBad{
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
#page_getting_started > .stats:first-of-type{
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#page_getting_started .stats {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.getting_started_windows{
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
#miningPorts > .stats{
|
||||
width: auto !important;
|
||||
margin-right: 70px;
|
||||
margin-left: 15px;
|
||||
display: inline-block;
|
||||
}
|
||||
#miningPorts > .stats:last-child{
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#getting_started_list > li{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#cpuminer_code{
|
||||
display: inline-block;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
footer{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
footer > div{
|
||||
margin: 10px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
$(function(){
|
||||
|
||||
$('#siteInfo').load('info.html');
|
||||
/*$.get('info.html', function(html){
|
||||
$('#siteInfo').html(html);
|
||||
}, 'html');*/
|
||||
|
||||
$.get(api + '/stats', function(data){
|
||||
renderStats(data);
|
||||
updateMarkets();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function fetchLiveStats() {
|
||||
$.ajax({
|
||||
url: api + '/live_stats',
|
||||
dataType: 'json',
|
||||
cache: 'false'
|
||||
}).done(function(data){
|
||||
renderStats(data);
|
||||
}).fail(function(){
|
||||
console.log('live stats long polling connection failed');
|
||||
}).always(function () {
|
||||
fetchLiveStats();
|
||||
});
|
||||
}
|
||||
|
||||
fetchLiveStats();
|
||||
|
||||
|
||||
window.onhashchange = function(){
|
||||
pageRouter();
|
||||
};
|
||||
|
||||
|
||||
|
||||
pageRouter();
|
||||
|
||||
$('#networkLastBlockFound,#poolLastBlockFound,#yourLastShare,#marketLastUpdated').timeago();
|
||||
|
||||
var $miningPorts = $('#miningPorts');
|
||||
var miningPortTemplate = $miningPorts.html();
|
||||
$miningPorts.empty();
|
||||
|
||||
var lookupBtnHtml = $('#lookUp').html();
|
||||
|
||||
var addressEventSource;
|
||||
var addressTimeout;
|
||||
|
||||
$('#lookUp').click(function(){
|
||||
|
||||
var address = $('#yourStatsInput').val().trim();
|
||||
if (!address){
|
||||
$('#yourStatsInput').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#addressError').hide();
|
||||
$('.yourStats').hide();
|
||||
$(this).html('<i class="fa fa-refresh fa-spin"></i> Searching...');
|
||||
if (addressEventSource) addressEventSource.abort();
|
||||
if (addressTimeout) clearTimeout(addressTimeout);
|
||||
|
||||
function fetchAddressStats(longpoll){
|
||||
addressEventSource = $.ajax({
|
||||
url: api + '/stats_address',
|
||||
data: {
|
||||
address: address,
|
||||
longpoll: longpoll
|
||||
},
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
success: function(data){
|
||||
|
||||
$('#lookUp').html(lookupBtnHtml);
|
||||
|
||||
if (!data.stats){
|
||||
$('.yourStats').hide();
|
||||
$('#addressError').text(data.error).show();
|
||||
|
||||
if (addressTimeout) clearTimeout(addressTimeout);
|
||||
addressTimeout = setTimeout(function(){
|
||||
fetchAddressStats(false);
|
||||
}, 2000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$('#addressError').hide();
|
||||
$('#yourAddressDisplay').text(address);
|
||||
if (data.stats.lastShare)
|
||||
$('#yourLastShare').timeago('update', new Date(parseInt(data.stats.lastShare) * 1000).toISOString());
|
||||
else
|
||||
$('#yourLastShare').text('Never');
|
||||
$('#yourHashrateHolder').text((data.stats.hashrate || '0 H') + '/sec');
|
||||
$('#yourHashes').text(data.stats.hashes || '0');
|
||||
$('#yourPaid').text(getReadableCoins(data.stats.paid) + ' ' + data.stats.symbol);
|
||||
$('#yourPendingBalance').text(getReadableCoins(data.stats.balance) + ' ' + data.stats.symbol);
|
||||
|
||||
$('.yourStats').show();
|
||||
docCookies.setItem('mining_address', address, Infinity);
|
||||
|
||||
fetchAddressStats(true);
|
||||
|
||||
},
|
||||
error: function(e){
|
||||
if (e.statusText === 'abort') return;
|
||||
$('#lookUp').html(lookupBtnHtml);
|
||||
$('#addressError').text('Connection error').show();
|
||||
|
||||
if (addressTimeout) clearTimeout(addressTimeout);
|
||||
addressTimeout = setTimeout(function(){
|
||||
fetchAddressStats(false);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
fetchAddressStats(false);
|
||||
});
|
||||
|
||||
|
||||
var lastPortsJson = '';
|
||||
var stats_update = $('#stats_updated')[0];
|
||||
|
||||
|
||||
var cryptonatorMarkets;
|
||||
|
||||
function updateMarkets(){
|
||||
var completedFetches = 0;
|
||||
var marketsData = [];
|
||||
for (var i = 0; i < cryptonatorMarkets.length; i++){
|
||||
(function(i){
|
||||
$.get('https://www.cryptonator.com/api/ticker/' + cryptonatorMarkets[i], function(data){
|
||||
marketsData[i] = data;
|
||||
completedFetches++;
|
||||
if (completedFetches !== cryptonatorMarkets.length) return;
|
||||
|
||||
var $marketHeader = $('#marketHeader');
|
||||
$('.marketTicker').remove();
|
||||
for (var f = marketsData.length - 1; f >= 0 ; f--){
|
||||
var price = parseFloat(marketsData[f].ticker.price);
|
||||
|
||||
if (price > 1) price = Math.round(price * 100) / 100;
|
||||
else price = marketsData[f].ticker.price;
|
||||
|
||||
$marketHeader.after('<div class="marketTicker">' + marketsData[f].ticker.base + ': <span>' + price + ' ' + marketsData[f].ticker.target + '</span></div>');
|
||||
}
|
||||
$('#marketLastUpdated').timeago('update', new Date(marketsData[0].timestamp * 1000).toISOString());
|
||||
}, 'json');
|
||||
})(i);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(updateMarkets, 300000); //poll market data every 5 minutes
|
||||
|
||||
function renderStats(stats){
|
||||
|
||||
$('#coinName').text(stats.config.coin);
|
||||
|
||||
$('#networkHashrate').text(getReadableHashRateString(stats.network.difficulty / 60) + '/sec');
|
||||
|
||||
$('#networkLastBlockFound').timeago('update', new Date(stats.network.timestamp * 1000).toISOString());
|
||||
$('#networkDifficulty').text(stats.network.difficulty);
|
||||
$('#blockchainHeight').text(stats.network.height);
|
||||
$('#networkLastReward').text((stats.network.reward / coinUnits).toFixed(4) + ' ' + stats.config.symbol);
|
||||
$('#lastHash').attr('href', blockchainExplorer + stats.network.hash).text(stats.network.hash.substr(0, 13) + '...');
|
||||
|
||||
$('#poolHashrate').text(getReadableHashRateString(stats.pool.hashrate) + '/sec');
|
||||
|
||||
|
||||
cryptonatorMarkets = cryptonatorWidget;
|
||||
|
||||
|
||||
if (stats.pool.lastBlockFound){
|
||||
var d = new Date(parseInt(stats.pool.lastBlockFound)).toISOString();
|
||||
$('#poolLastBlockFound').timeago('update', d);
|
||||
}
|
||||
else{
|
||||
$('#poolLastBlockFound').removeAttr('title').data('ts', '').text('Never');
|
||||
}
|
||||
$('#poolRoundHashes').text(stats.pool.roundHashes);
|
||||
$('#poolMiners').text(stats.pool.miners);
|
||||
$('#poolFee').text(stats.config.fee + '%');
|
||||
|
||||
if (stats.config.donation && stats.config.donation > 0)
|
||||
$('#donationPercent').text(' + ' + stats.config.donation + '% development funding');
|
||||
else
|
||||
$('#donationPercent').text('');
|
||||
|
||||
$('#blockSolvedTime').text(getReadableTime(stats.network.difficulty / stats.pool.hashrate));
|
||||
|
||||
$('#miningPoolHost').text(poolHost);
|
||||
|
||||
var portsJson = JSON.stringify(stats.config.ports);
|
||||
if (lastPortsJson !== portsJson) {
|
||||
lastPortsJson = portsJson;
|
||||
var $miningPortChildren = [];
|
||||
for (var i = 0; i < stats.config.ports.length; i++) {
|
||||
var portData = stats.config.ports[i];
|
||||
var $portChild = $(miningPortTemplate);
|
||||
$portChild.find('.miningPort').text(portData.port);
|
||||
$portChild.find('.miningPortDiff').text(portData.difficulty);
|
||||
$portChild.find('.miningPortDesc').text(portData.desc);
|
||||
$miningPortChildren.push($portChild);
|
||||
}
|
||||
$miningPorts.empty().append($miningPortChildren);
|
||||
}
|
||||
|
||||
|
||||
$('#easyminer_link').attr('href', easyminerDownload);
|
||||
$('#simplewallet_download').attr('href', simplewalletDownload);
|
||||
$('#cpuminer_code').text('minerd -a cryptonight -o stratum+tcp://'
|
||||
+ poolHost + ':'
|
||||
+ (stats.config.ports[0] || {}).port
|
||||
+ ' -u address -p x');
|
||||
|
||||
var kiwiIrc = 'https://kiwiirc.com/client/' + irc;
|
||||
if ($('#kiwi_irc').attr('src') !== kiwiIrc){
|
||||
$('#kiwi_irc').attr('src', kiwiIrc);
|
||||
}
|
||||
|
||||
$('#emailLink').attr('href', 'mailto:' + email).text(email);
|
||||
|
||||
$('#blocksMaturityCount').text(stats.config.depth);
|
||||
|
||||
renderBlocks(stats.pool.blocks, stats.config.depth, stats.network.height, blockchainExplorer);
|
||||
|
||||
$('#blocks_rows').find('tr[class=""]').each(function(){
|
||||
var height = parseInt(this.children[0].innerHTML);
|
||||
this.children[1].innerHTML = getMaturity(stats.config.depth, stats.network.height, height);
|
||||
});
|
||||
|
||||
$('#poolVersion').text(stats.config.version);
|
||||
|
||||
stats_update.style.transition = 'opacity 100ms ease-out';
|
||||
stats_update.style.opacity = 1;
|
||||
setTimeout(function(){
|
||||
stats_update.style.transition = 'opacity 7000ms linear';
|
||||
stats_update.style.opacity = 0;
|
||||
}, 500);
|
||||
|
||||
}
|
||||
|
||||
function getMaturity(depth, chainHeight, blockHeight){
|
||||
var toGo = depth - (chainHeight - blockHeight);
|
||||
if (toGo < 1) return '';
|
||||
return '' + toGo + ' to go';
|
||||
}
|
||||
|
||||
var lastBlocksJson = '';
|
||||
|
||||
function renderBlocks(blocksResults, depth, chainHeight, explorer){
|
||||
|
||||
var blocksJson = JSON.stringify(blocksResults);
|
||||
if (lastBlocksJson === blocksJson) return;
|
||||
lastBlocksJson = blocksJson;
|
||||
|
||||
var blocks = [];
|
||||
for (var status in blocksResults){
|
||||
var blockArray = blocksResults[status];
|
||||
for (var i = 0; i < blockArray.length; i++){
|
||||
var blockData = blockArray[i].split(':');
|
||||
blockData[0] = parseInt(blockData[0]);
|
||||
blockData.unshift(status);
|
||||
blocks.push(blockData);
|
||||
}
|
||||
}
|
||||
blocks.sort(function(a, b){
|
||||
return b[1] - a[1];
|
||||
});
|
||||
|
||||
|
||||
|
||||
var blockStatusClasses = {
|
||||
'pending': '',
|
||||
'unlocked': 'success',
|
||||
'orphaned': 'danger'
|
||||
};
|
||||
|
||||
var formatDate = function(time){
|
||||
if (!time) return '';
|
||||
return new Date(parseInt(time) * 1000).toLocaleString();
|
||||
};
|
||||
|
||||
var formatLuck = function(percent){
|
||||
if (!percent) return '';
|
||||
return '<span class="' + (percent < 100 ? 'luckBad' : 'luckGood') + '">' + percent + '%</span>';
|
||||
};
|
||||
|
||||
var rows = '';
|
||||
|
||||
var totalLuck = 0;
|
||||
var totalLuckBlocks = 0;
|
||||
|
||||
for (var i = 0; i < blocks.length; i++){
|
||||
var block = blocks[i];
|
||||
var blockLuck = null;
|
||||
if (block[5]){
|
||||
blockLuck = Math.round(parseInt(block[2]) / parseInt(block[5]) * 100);
|
||||
totalLuck += blockLuck;
|
||||
totalLuckBlocks++;
|
||||
}
|
||||
rows += ('<tr title="' + block[0] + '" class="' + blockStatusClasses[block[0]] + '">' +
|
||||
'<td>' + block[1] + '</td>' +
|
||||
'<td>' + (block[0] === 'pending' ? getMaturity(depth, chainHeight, blocks[1]) : '') + '</td>' +
|
||||
'<td>' + block[2] + '</td>' +
|
||||
'<td><a target="_blank" href="' + explorer + block[3] + '">' + block[3] + '</a></td>' +
|
||||
'<td>' + formatDate(block[4]) + '</td>' +
|
||||
'<td>' + formatLuck(blockLuck) + '</td>' +
|
||||
'</tr>');
|
||||
}
|
||||
|
||||
$('#blocks_rows').empty().append(rows);
|
||||
$('#averageBlockLuck').html(formatLuck(Math.round(totalLuck / totalLuckBlocks)));
|
||||
$('#blocksCountPending').text(Object.keys(blocksResults['pending']).length);
|
||||
$('#blocksCountUnlocked').text(Object.keys(blocksResults['unlocked']).length);
|
||||
$('#blocksCountOrphaned').text(Object.keys(blocksResults['orphaned']).length);
|
||||
|
||||
}
|
||||
|
||||
var docCookies = {
|
||||
getItem: function (sKey) {
|
||||
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
|
||||
},
|
||||
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
|
||||
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
|
||||
var sExpires = "";
|
||||
if (vEnd) {
|
||||
switch (vEnd.constructor) {
|
||||
case Number:
|
||||
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
|
||||
break;
|
||||
case String:
|
||||
sExpires = "; expires=" + vEnd;
|
||||
break;
|
||||
case Date:
|
||||
sExpires = "; expires=" + vEnd.toUTCString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
|
||||
return true;
|
||||
},
|
||||
removeItem: function (sKey, sPath, sDomain) {
|
||||
if (!sKey || !this.hasItem(sKey)) { return false; }
|
||||
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
|
||||
return true;
|
||||
},
|
||||
hasItem: function (sKey) {
|
||||
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
|
||||
},
|
||||
keys: /* optional method: you can safely remove it! */ function () {
|
||||
var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
|
||||
for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
|
||||
return aKeys;
|
||||
}
|
||||
};
|
||||
|
||||
var coinDecimals = coinUnits.toString().length - 1;
|
||||
|
||||
function getReadableCoins(coins){
|
||||
return (parseInt(coins || 0) / coinUnits).toFixed(coinDecimals);
|
||||
}
|
||||
|
||||
function getReadableTime(seconds){
|
||||
|
||||
var units = [
|
||||
[60, 'second'],
|
||||
[60, 'minute'],
|
||||
[24, 'hour'],
|
||||
[12, 'day'],
|
||||
[7, 'week'],
|
||||
[30, 'month'],
|
||||
[12, 'year']
|
||||
];
|
||||
|
||||
function formatAmounts(amount, unit){
|
||||
var rounded = Math.round(amount);
|
||||
return '' + rounded + ' ' + unit + (rounded > 1 ? 's' : '');
|
||||
}
|
||||
|
||||
var amount = seconds;
|
||||
for (var i = 0; i < units.length; i++){
|
||||
if (amount < units[i][0])
|
||||
return formatAmounts(amount, units[i][1]);
|
||||
amount = amount / units[i][0];
|
||||
}
|
||||
return formatAmounts(amount, units[units.length - 1][1]);
|
||||
|
||||
}
|
||||
|
||||
function getReadableHashRateString(hashrate){
|
||||
var i = 0;
|
||||
var byteUnits = [' H', ' KH', ' MH', ' GH', ' TH', ' PH' ];
|
||||
while (hashrate > 1024){
|
||||
hashrate = hashrate / 1024;
|
||||
i++;
|
||||
}
|
||||
return hashrate.toFixed(2) + byteUnits[i];
|
||||
}
|
||||
|
||||
|
||||
function pageRouter(){
|
||||
$('.page').hide();
|
||||
$('.hot_link').parent().removeClass('active');
|
||||
var $link = $('a.hot_link[href="' + (window.location.hash || '#') + '"]');
|
||||
|
||||
$link.parent().addClass('active');
|
||||
var page = $link.data('page');
|
||||
$('#' + page).show();
|
||||
}
|
||||
|
||||
var address = docCookies.getItem('mining_address');
|
||||
|
||||
if (address){
|
||||
$('#yourStatsInput').val(address);
|
||||
$('#lookUp').click();
|
||||
}
|
||||
|
||||
$('#yourStatsInput').keyup(function(e){
|
||||
if(e.keyCode === 13)
|
||||
$('#lookUp').click();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand"><span id="coinName"></span> Mining Pool</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a class="hot_link" data-page="page_home" href="#"><i class="fa fa-home"></i> Home</a></li>
|
||||
<li><a class="hot_link" data-page="page_pool_blocks" href="#pool_blocks"><i class="fa fa-cubes"></i> Pool Blocks</a></li>
|
||||
<li><a class="hot_link" data-page="page_getting_started" href="#getting_started"><i class="fa fa-rocket"></i> Getting Started</a></li>
|
||||
<li><a class="hot_link" data-page="page_community" href="#community"><i class="fa fa-comments"></i> Community</a></li>
|
||||
</ul>
|
||||
<div id="stats_updated">Stats Updated <i class="fa fa-bolt"></i></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="page" id="page_home">
|
||||
|
||||
<div id="siteInfo"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 stats">
|
||||
<h3>Network</h3>
|
||||
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="networkHashrate"></span></div>
|
||||
<div><i class="fa fa-clock-o"></i> Block Found: <span id="networkLastBlockFound"></span></div>
|
||||
<div><i class="fa fa-unlock-alt"></i> Difficulty: <span id="networkDifficulty"></span></div>
|
||||
<div><i class="fa fa-bars"></i> Blockchain Height: <span id="blockchainHeight"></span></div>
|
||||
<div><i class="fa fa-money"></i> Last Reward: <span id="networkLastReward"></span></div>
|
||||
<div><i class="fa fa-paw"></i> Last Hash: <a id="lastHash" target="_blank"></a></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 stats">
|
||||
<h3>Pool</h3>
|
||||
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="poolHashrate"></span></div>
|
||||
<div><i class="fa fa-clock-o"></i> Block Found: <span id="poolLastBlockFound"></span></div>
|
||||
<div><i class="fa fa-cloud-upload"></i> Round Hashes: <span id="poolRoundHashes"></span></div>
|
||||
<div><i class="fa fa-users"></i> Connected Miners: <span id="poolMiners"></span></div>
|
||||
<div><i class="fa fa-money"></i> Fee: <span id="poolFee"></span><span id="donationPercent"></span></div>
|
||||
<div><i class="fa fa-history"></i> Block Found Every: <span id="blockSolvedTime"></span> (est.)</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 stats">
|
||||
<h3 id="marketHeader">Market</h3>
|
||||
<div class="marketFooter">Updated: <span id="marketLastUpdated"></span></div>
|
||||
<div class="marketFooter">Powered by <a href="https://www.cryptonator.com/">Cryptonator</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<h3>Your Stats</h3>
|
||||
<label for="yourStatsInput"><i class="fa fa-key"></i> Address</label><br>
|
||||
<input class="form-control" id="yourStatsInput" type="text">
|
||||
<button class="btn btn-default" id="lookUp"><i class="fa fa-search"></i> Lookup</button>
|
||||
<div id="addressError"></div>
|
||||
<div class="yourStats"><i class="fa fa-key"></i> Address: <span id="yourAddressDisplay"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-bank"></i> Pending Balance: <span id="yourPendingBalance"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-money"></i> Total Paid: <span id="yourPaid"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-clock-o"></i> Last Share Submitted: <span id="yourLastShare"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-tachometer"></i> Hash Rate: <span id="yourHashrateHolder"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-cloud-upload"></i> Total Hashes Submitted: <span id="yourHashes"></span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="page" id="page_pool_blocks">
|
||||
<div class="blocksStatHolder">
|
||||
<h4>Block Candidates</h4>
|
||||
<span><span id="blocksCountPending"></span> Maturing</span>
|
||||
<span class="bg-success"><span id="blocksCountUnlocked"></span> Unlocked</span>
|
||||
<span class="bg-danger"><span id="blocksCountOrphaned"></span> Orphaned</span>
|
||||
<span class="bg-info">Maturity requires <span id="blocksMaturityCount"></span> blocks</span>
|
||||
<span><span id="averageBlockLuck"></span> Luck Average</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="fa fa-bars"></i> Height</th>
|
||||
<th title="How many more blocks network must mine before this block is matured"><i class="fa fa-link"></i> Maturity</th>
|
||||
<th><i class="fa fa-unlock-alt"></i> Difficulty</th>
|
||||
<th><i class="fa fa-paw"></i> Block Hash</th>
|
||||
<th><i class="fa fa-clock-o"></i> Time Found</th>
|
||||
<th><i class="fa fa-star-half-o"></i> Luck</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="blocks_rows">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page_getting_started">
|
||||
<h1>Getting Started</h1>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stats">
|
||||
<h4>Mining Host</h4>
|
||||
<div><i class="fa fa-cloud"></i> Pool Address: <span id="miningPoolHost"></span></div>
|
||||
</div>
|
||||
|
||||
<h4>Mining Ports</h4>
|
||||
<div id="miningPorts" class="row">
|
||||
<div class="stats">
|
||||
<div><i class="fa fa-tachometer"></i> Port: <span class="miningPort"></span></div>
|
||||
<div><i class="fa fa-unlock-alt"></i> Starting Difficulty: <span class="miningPortDiff"></span></div>
|
||||
<div><i class="fa fa-question"></i> Description: <span class="miningPortDesc"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>For <i class="fa fa-windows"></i> Windows users</h4>
|
||||
<br>
|
||||
<p class="getting_started_windows">
|
||||
You can <a class="btn btn-default btn-sm" target="_blank" id="easyminer_link"><i class="fa fa-download"></i> Download</a> and run <a target="_blank" href="https://github.com/zone117x/cryptonote-easy-miner">cryptonote-easy-miner</a> <i class="fa fa-github"></i> which will automatically generate your wallet address
|
||||
and manage multiple instances of simpleminer to take advantage of all your CPU Cores.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>Or download the the binaries for your system</h4>
|
||||
<br>
|
||||
<ol id="getting_started_list">
|
||||
<li><a id="simplewallet_download" target="_blank" class="btn btn-default btn-sm"><i class="fa fa-download"></i> Download</a> simplewallet for your system</li>
|
||||
<li>Run simplewallet to generate your public address</li>
|
||||
<li><a target="_blank" class="btn btn-default btn-sm" href="https://github.com/LucasJones/cpuminer-multi/releases/"><i class="fa fa-download"></i> Download</a> CPUMiner-multi</li>
|
||||
<li>Choose a port and point your miner to our pool with your address, for example:
|
||||
<br><br>
|
||||
<code id="cpuminer_code"></code>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page_community">
|
||||
<h3>Chat Room</h3>
|
||||
<iframe id="kiwi_irc" style="border:0; width:100%; height:500px;"></iframe>
|
||||
<h3>Contact</h3>
|
||||
<p>Email pool support at <a id="emailLink" href=""></a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="text-muted">
|
||||
Powered by <a target="_blank" href="//github.com/zone117x/node-cryptonote-pool"><i class="fa fa-github"></i> node-cryptonote-pool</a>
|
||||
<span id="poolVersion"></span>
|
||||
created by Matthew Little & open sourced under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GPL</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,10 +0,0 @@
|
|||
<!-- Insert your pool's unique HTML here -->
|
||||
|
||||
|
||||
<!-- Example:
|
||||
|
||||
<ul>
|
||||
<li>Update July 24th, we have decreased pool fees to 1%, enjoy!</li>
|
||||
</lu>
|
||||
|
||||
-->
|
|
@ -0,0 +1,155 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
#statsHolder{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.luckGood{
|
||||
color: darkgreen;
|
||||
}
|
||||
.luckBad{
|
||||
color: darkred;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="config.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
$(function(){
|
||||
getStats();
|
||||
});
|
||||
|
||||
|
||||
var docCookies = {
|
||||
getItem: function (sKey) {
|
||||
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
|
||||
},
|
||||
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
|
||||
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
|
||||
var sExpires = "";
|
||||
if (vEnd) {
|
||||
switch (vEnd.constructor) {
|
||||
case Number:
|
||||
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
|
||||
break;
|
||||
case String:
|
||||
sExpires = "; expires=" + vEnd;
|
||||
break;
|
||||
case Date:
|
||||
sExpires = "; expires=" + vEnd.toUTCString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
|
||||
return true;
|
||||
},
|
||||
removeItem: function (sKey, sPath, sDomain) {
|
||||
if (!sKey || !this.hasItem(sKey)) { return false; }
|
||||
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
|
||||
return true;
|
||||
},
|
||||
hasItem: function (sKey) {
|
||||
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function getReadableCoins(coins){
|
||||
return (parseInt(coins || 0) / coinUnits).toFixed(coinUnits.toString().length - 1);
|
||||
}
|
||||
|
||||
function getStats(promptPassword){
|
||||
|
||||
var password = docCookies.getItem('password');
|
||||
|
||||
|
||||
if (!password || promptPassword)
|
||||
password = prompt('Enter admin password');
|
||||
|
||||
|
||||
$('#loading').show();
|
||||
$.ajax({
|
||||
url: api + '/admin_stats',
|
||||
data: {password: password},
|
||||
success: function(data){
|
||||
docCookies.setItem('password', password, Infinity);
|
||||
$('#loading').hide();
|
||||
renderData(data);
|
||||
},
|
||||
error: function(e){
|
||||
docCookies.removeItem('password');
|
||||
getStats(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var formatLuck = function(difficulty, shares){
|
||||
|
||||
if (difficulty > shares){
|
||||
var percent = 100 - Math.round(shares / difficulty * 100);
|
||||
return '<span class="luckGood">' + percent + '%</span>';
|
||||
}
|
||||
else{
|
||||
var percent = (100 - Math.round(difficulty / shares * 100)) * -1;
|
||||
return '<span class="luckBad">' + percent + '%</span>';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function renderData(data){
|
||||
$('#totalOwed').text(getReadableCoins(data.totalOwed));
|
||||
$('#totalPaid').text(getReadableCoins(data.totalPaid));
|
||||
$('#totalMined').text(getReadableCoins(data.totalRevenue));
|
||||
$('#profit').text(getReadableCoins(data.totalRevenue - data.totalOwed - data.totalPaid));
|
||||
$('#averageLuck').html(formatLuck(data.totalDiff, data.totalShares));
|
||||
$('#orphanPercent').text((data.blocksOrphaned / data.blocksUnlocked * 100).toFixed(2));
|
||||
$('#registeredAddresses').text(data.totalWorkers);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h3>Admin Center <i id="loading" class="fa fa-circle-o-notch fa-spin"></i></h3>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>Stats</h4>
|
||||
<dl class="dl-horizontal" id="statsHolder">
|
||||
<dt>Total Owed</dt><dd id="totalOwed">...</dd>
|
||||
<dt>Total Paid</dt><dd id="totalPaid">...</dd>
|
||||
<dt>Total Mined</dt><dd id="totalMined">...</dd>
|
||||
<dt>Profit (before tx fees)</dt><dd id="profit">...</dd>
|
||||
<dt>Average Luck</dt><dd id="averageLuck">...</dd>
|
||||
<dt>Orphan Percent</dt><dd id="orphanPercent">...</dd>
|
||||
<dt>Registered Addresses</dt><dd id="registeredAddresses">...</dd>
|
||||
</dl>
|
||||
<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>Miner Lookup</h4>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -12,6 +12,6 @@ var cryptonatorWidget = ["XMR-BTC", "XMR-USD", "XMR-EUR", "XMR-GBP"];
|
|||
|
||||
var easyminerDownload = "https://github.com/zone117x/cryptonote-easy-miner/releases/";
|
||||
|
||||
var simplewalletDownload = "http://bit.ly/monero-starter-pack";
|
||||
var blockchainExplorer = "http://monerochain.info/block/";
|
||||
|
||||
var blockchainExplorer = "http://monerochain.info/block/";
|
||||
var transactionExplorer = "http://monerochain.info/tx/";
|
|
@ -0,0 +1,359 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
|
||||
|
||||
<title>Cryptonote Mining Pool</title>
|
||||
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.0/jquery.timeago.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
|
||||
|
||||
<script src="config.js"></script>
|
||||
<script src="custom.js"></script>
|
||||
<link href="custom.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
#coinName{
|
||||
text-transform: capitalize;
|
||||
}
|
||||
body {
|
||||
padding-top: 90px;
|
||||
padding-bottom: 80px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.container{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
#loading{
|
||||
font-size: 2em;
|
||||
}
|
||||
.stats {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.stats:last-child{
|
||||
width: auto;
|
||||
}
|
||||
.stats > h3 > i{
|
||||
font-size: 0.80em;
|
||||
width: 21px;
|
||||
}
|
||||
.stats > div{
|
||||
padding: 5px 0;
|
||||
}
|
||||
.stats > div > .fa {
|
||||
width: 25px;
|
||||
}
|
||||
.stats > div > span:first-of-type{
|
||||
font-weight: bold;
|
||||
}
|
||||
#stats_updated{
|
||||
opacity: 0;
|
||||
float: right;
|
||||
margin-left: 30px;
|
||||
color: #e8e8e8;
|
||||
line-height: 47px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
footer{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
footer > div{
|
||||
margin: 10px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
|
||||
var docCookies = {
|
||||
getItem: function (sKey) {
|
||||
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
|
||||
},
|
||||
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
|
||||
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
|
||||
var sExpires = "";
|
||||
if (vEnd) {
|
||||
switch (vEnd.constructor) {
|
||||
case Number:
|
||||
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
|
||||
break;
|
||||
case String:
|
||||
sExpires = "; expires=" + vEnd;
|
||||
break;
|
||||
case Date:
|
||||
sExpires = "; expires=" + vEnd.toUTCString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
|
||||
return true;
|
||||
},
|
||||
removeItem: function (sKey, sPath, sDomain) {
|
||||
if (!sKey || !this.hasItem(sKey)) { return false; }
|
||||
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
|
||||
return true;
|
||||
},
|
||||
hasItem: function (sKey) {
|
||||
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.update = function(txt){
|
||||
var el = this[0];
|
||||
if (el.textContent !== txt)
|
||||
el.textContent = txt;
|
||||
return this;
|
||||
};
|
||||
|
||||
function updateTextClasses(className, text){
|
||||
var els = document.getElementsByClassName(className);
|
||||
for (var i = 0; i < els.length; i++){
|
||||
var el = els[i];
|
||||
if (el.textContent !== text)
|
||||
el.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
function updateText(elementId, text){
|
||||
var el = document.getElementById(elementId);
|
||||
if (el.textContent !== text){
|
||||
el.textContent = text;
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
var currentPage;
|
||||
var lastStats;
|
||||
|
||||
function getReadableCoins(coins, digits, withoutSymbol){
|
||||
var amount = (parseInt(coins || 0) / coinUnits).toFixed(digits || coinUnits.toString().length - 1);
|
||||
return amount + (withoutSymbol ? '' : (' ' + lastStats.config.symbol));
|
||||
}
|
||||
|
||||
function formatDate(time){
|
||||
if (!time) return '';
|
||||
return new Date(parseInt(time) * 1000).toLocaleString();
|
||||
}
|
||||
|
||||
function formatPaymentLink(hash){
|
||||
return '<a target="_blank" href="' + transactionExplorer + hash + '">' + hash + '</a>';
|
||||
}
|
||||
|
||||
function getPaymentRowElement(payment, jsonString){
|
||||
|
||||
var row = document.createElement('tr');
|
||||
row.setAttribute('data-json', jsonString);
|
||||
row.setAttribute('data-time', payment.time);
|
||||
row.setAttribute('id', 'paymentRow' + payment.time);
|
||||
|
||||
row.innerHTML = getPaymentCells(payment);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
function parsePayment(time, serializedPayment){
|
||||
var parts = serializedPayment.split(':');
|
||||
return {
|
||||
time: parseInt(time),
|
||||
hash: parts[0],
|
||||
amount: parts[1],
|
||||
fee: parts[2],
|
||||
mixin: parts[3],
|
||||
recipients: parts[4]
|
||||
};
|
||||
}
|
||||
|
||||
function renderPayments(paymentsResults){
|
||||
|
||||
var $paymentsRows = $('#payments_rows');
|
||||
|
||||
for (var i = 0; i < paymentsResults.length; i += 2){
|
||||
|
||||
var payment = parsePayment(paymentsResults[i + 1], paymentsResults[i]);
|
||||
|
||||
var paymentJson = JSON.stringify(payment);
|
||||
|
||||
var existingRow = document.getElementById('paymentRow' + payment.time);
|
||||
|
||||
if (existingRow && existingRow.getAttribute('data-json') !== paymentJson){
|
||||
$(existingRow).replaceWith(getPaymentRowElement(payment, paymentJson));
|
||||
}
|
||||
else if (!existingRow){
|
||||
|
||||
var paymentElement = getPaymentRowElement(payment, paymentJson);
|
||||
|
||||
var inserted = false;
|
||||
var rows = $paymentsRows.children().get();
|
||||
for (var f = 0; f < rows.length; f++) {
|
||||
var pTime = parseInt(rows[f].getAttribute('data-time'));
|
||||
if (pTime < payment.time){
|
||||
inserted = true;
|
||||
$(rows[f]).before(paymentElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted)
|
||||
$paymentsRows.append(paymentElement);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function pulseLiveUpdate(){
|
||||
var stats_update = document.getElementById('stats_updated');
|
||||
stats_update.style.transition = 'opacity 100ms ease-out';
|
||||
stats_update.style.opacity = 1;
|
||||
setTimeout(function(){
|
||||
stats_update.style.transition = 'opacity 7000ms linear';
|
||||
stats_update.style.opacity = 0;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
window.onhashchange = function(){
|
||||
routePage();
|
||||
};
|
||||
|
||||
|
||||
function fetchLiveStats() {
|
||||
$.ajax({
|
||||
url: api + '/live_stats',
|
||||
dataType: 'json',
|
||||
cache: 'false'
|
||||
}).done(function(data){
|
||||
pulseLiveUpdate();
|
||||
lastStats = data;
|
||||
updateIndex();
|
||||
currentPage.update();
|
||||
}).always(function () {
|
||||
fetchLiveStats();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var xhrPageLoading;
|
||||
function routePage(loadedCallback) {
|
||||
|
||||
if (currentPage) currentPage.destroy();
|
||||
$('#page').html('');
|
||||
$('#loading').show();
|
||||
|
||||
if (xhrPageLoading)
|
||||
xhrPageLoading.abort();
|
||||
|
||||
$('.hot_link').parent().removeClass('active');
|
||||
var $link = $('a.hot_link[href="' + (window.location.hash || '#') + '"]');
|
||||
|
||||
$link.parent().addClass('active');
|
||||
var page = $link.data('page');
|
||||
|
||||
xhrPageLoading = $.ajax({
|
||||
url: 'pages/' + page,
|
||||
cache: false,
|
||||
success: function (data) {
|
||||
$('#loading').hide();
|
||||
$('#page').show().html(data);
|
||||
currentPage.update();
|
||||
if (loadedCallback) loadedCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateIndex(){
|
||||
updateText('coinName', lastStats.config.coin);
|
||||
updateText('poolVersion', lastStats.config.version);
|
||||
}
|
||||
|
||||
$(function(){
|
||||
$.get(api + '/stats', function(data){
|
||||
lastStats = data;
|
||||
updateIndex();
|
||||
routePage(fetchLiveStats);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand"><span id="coinName"></span> Mining Pool</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
|
||||
<li class="active"><a class="hot_link" data-page="home.html" href="#">
|
||||
<i class="fa fa-home"></i> Home
|
||||
</a></li>
|
||||
|
||||
<li><a class="hot_link" data-page="getting_started.html" href="#getting_started">
|
||||
<i class="fa fa-rocket"></i> Getting Started
|
||||
</a></li>
|
||||
|
||||
<li><a class="hot_link" data-page="pool_blocks.html" href="#pool_blocks">
|
||||
<i class="fa fa-cubes"></i> Pool Blocks
|
||||
</a></li>
|
||||
|
||||
<li><a class="hot_link" data-page="payments.html" href="#payments">
|
||||
<i class="fa fa-paper-plane-o"></i> Payments
|
||||
</a></li>
|
||||
|
||||
<li><a class="hot_link" data-page="support.html" href="#support">
|
||||
<i class="fa fa-comments"></i> Support
|
||||
</a></li>
|
||||
|
||||
</ul>
|
||||
<div id="stats_updated">Stats Updated <i class="fa fa-bolt"></i></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div id="page"></div>
|
||||
|
||||
<p id="loading" class="text-center"><i class="fa fa-circle-o-notch fa-spin"></i></p>
|
||||
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="text-muted">
|
||||
Powered by <a target="_blank" href="//github.com/zone117x/node-cryptonote-pool"><i class="fa fa-github"></i> node-cryptonote-pool</a>
|
||||
<span id="poolVersion"></span>
|
||||
created by Matthew Little & open sourced under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GPL</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,206 @@
|
|||
<style>
|
||||
hr{
|
||||
border-top-color: #d9d9d9 !important;
|
||||
margin-top: 40px !important;
|
||||
margin-bottom: 40px !important;
|
||||
}
|
||||
#miningPorts > .stats{
|
||||
width: auto !important;
|
||||
margin-right: 70px;
|
||||
margin-left: 15px;
|
||||
display: inline-block;
|
||||
}
|
||||
#miningPorts > .stats:last-child{
|
||||
margin-right: 0;
|
||||
}
|
||||
.getting_started_windows{
|
||||
line-height: 40px;
|
||||
}
|
||||
#getting_started_list > li{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#mining_apps > tr:first-child > td{
|
||||
padding-top: 35px;
|
||||
}
|
||||
#mining_apps > tr > td{
|
||||
border-top: none;
|
||||
}
|
||||
#mining_apps > tr:nth-child(even) > td{
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
#mining_apps > tr:nth-child(odd) > td{
|
||||
padding-top: 20px;
|
||||
}
|
||||
#mining_apps{
|
||||
margin-top: 10px;
|
||||
}
|
||||
.miningAppTitle{
|
||||
font-weight: bold;
|
||||
}
|
||||
.exampleAddress{
|
||||
font-style: italic;
|
||||
}
|
||||
#cpuminer_code{
|
||||
display: inline-block;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<h3>Connection Details</h3>
|
||||
<div class="stats">
|
||||
<div><i class="fa fa-cloud"></i> Mining Pool Address: <span id="miningPoolHost"></span></div>
|
||||
</div>
|
||||
|
||||
<h4>Mining Ports</h4>
|
||||
<div id="miningPorts" class="row">
|
||||
<div class="stats">
|
||||
<div><i class="fa fa-tachometer"></i> Port: <span class="miningPort"></span></div>
|
||||
<div><i class="fa fa-unlock-alt"></i> Starting Difficulty: <span class="miningPortDiff"></span></div>
|
||||
<div><i class="fa fa-question"></i> Description: <span class="miningPortDesc"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>For <i class="fa fa-windows"></i> Windows users new to mining</h3>
|
||||
<p class="getting_started_windows">
|
||||
You can <a class="btn btn-default btn-sm" target="_blank" id="easyminer_link"><i class="fa fa-download"></i> Download</a>
|
||||
and run <a target="_blank" href="https://github.com/zone117x/cryptonote-easy-miner">cryptonote-easy-miner</a> <i class="fa fa-github"></i>
|
||||
which will automatically generate your wallet address and run CPUMiner with the proper parameters.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Wallet & Daemon Software</h3>
|
||||
<p>
|
||||
<ul>
|
||||
<li><a target="_blank" href="http://monero.cc/getting-started/">Getting started with Monero</a></li>
|
||||
<li>Monero information and news on its <a href="https://bitcointalk.org/index.php?topic=583449.0">BitcoinTalk announcement thread</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Mining Apps</h3>
|
||||
<div class="yourStats table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="fa fa-book"></i> App Name</th>
|
||||
<th><i class="fa fa-car"></i> Architecture</th>
|
||||
<th><i class="fa fa-download"></i> Downloads</th>
|
||||
<th><i class="fa fa-comments"></i> Discussion</th>
|
||||
<th><i class="fa fa-file-code-o"></i> Source Code</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mining_apps">
|
||||
<tr>
|
||||
<td class="miningAppTitle">CPUMiner (forked by LucasJones & Wolf)</td>
|
||||
<td>CPU</td>
|
||||
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=632724">BitcoinTalk</a></td>
|
||||
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=632724">BitcoinTalk</a></td>
|
||||
<td><a target="_blank" href="https://github.com/wolf9466/cpuminer-multi">Github</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<span>Example:</span>
|
||||
<code>minerd -a cryptonight -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="miningAppTitle">YAM Miner (by yvg1900)</td>
|
||||
<td>CPU</td>
|
||||
<td><a target="_blank" href="https://mega.co.nz/#F!UlkU0RyR!E8n4CFkqVu0WoOnsJnQkSg">MEGA</a></td>
|
||||
<td><a target="_blank" href="https://twitter.com/yvg1900">Twitter</a></td>
|
||||
<td>Proprietary <i class="fa fa-frown-o"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<span>Example:</span>
|
||||
<code>yam -c x -M stratum+tcp://<span class="exampleAddress">YOUR_WALLET_ADDRESS</span>:x@<span class="exampleHost"></span>:<span class="examplePort"></span>/xmr</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="miningAppTitle">Claymore CPU Miner</td>
|
||||
<td>CPU</td>
|
||||
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=647251.0">BitcoinTalk</a></td>
|
||||
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=647251.0">BitcoinTalk</a></td>
|
||||
<td>Proprietary <i class="fa fa-frown-o"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<span>Example:</span>
|
||||
<code>NsCpuCNMiner64 -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="miningAppTitle">Claymore GPU Miner</td>
|
||||
<td>OpenCL (AMD)</td>
|
||||
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=638915.0">BitcoinTalk</a></td>
|
||||
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=638915.0">Discussion</a></td>
|
||||
<td>Proprietary <i class="fa fa-frown-o"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<span>Example:</span>
|
||||
<code>NsGpuCNMiner -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="miningAppTitle">ccminer (forked by tsiv)</td>
|
||||
<td>CUDA (Nvidia)</td>
|
||||
<td><a target="_blank" href="https://github.com/tsiv/ccminer-cryptonight/releases">Github</a></td>
|
||||
<td><a target="_blank" href="https://bitcointalk.org/index.php?topic=656841.msg7487737#msg7487737">BitcoinTalk</a></td>
|
||||
<td><a target="_blank" href="https://github.com/tsiv/ccminer-cryptonight">Github</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<span>Example:</span>
|
||||
<code>ccminer -o stratum+tcp://<span class="exampleHost"></span>:<span class="examplePort"></span> -u <span class="exampleAddress">YOUR_WALLET_ADDRESS</span> -p x</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
currentPage = {
|
||||
destroy: function(){
|
||||
|
||||
},
|
||||
update: function(){
|
||||
|
||||
var portsJson = JSON.stringify(lastStats.config.ports);
|
||||
if (lastPortsJson !== portsJson) {
|
||||
lastPortsJson = portsJson;
|
||||
var $miningPortChildren = [];
|
||||
for (var i = 0; i < lastStats.config.ports.length; i++) {
|
||||
var portData = lastStats.config.ports[i];
|
||||
var $portChild = $(miningPortTemplate);
|
||||
$portChild.find('.miningPort').text(portData.port);
|
||||
$portChild.find('.miningPortDiff').text(portData.difficulty);
|
||||
$portChild.find('.miningPortDesc').text(portData.desc);
|
||||
$miningPortChildren.push($portChild);
|
||||
}
|
||||
$miningPorts.empty().append($miningPortChildren);
|
||||
}
|
||||
|
||||
updateTextClasses('exampleHost', poolHost);
|
||||
updateTextClasses('examplePort', lastStats.config.ports[0].port.toString());
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('easyminer_link').setAttribute('href', easyminerDownload);
|
||||
document.getElementById('miningPoolHost').textContent = poolHost;
|
||||
|
||||
var lastPortsJson = '';
|
||||
var $miningPorts = $('#miningPorts');
|
||||
var miningPortTemplate = $miningPorts.html();
|
||||
$miningPorts.empty();
|
||||
|
||||
</script>
|
|
@ -0,0 +1,467 @@
|
|||
<style>
|
||||
.marketFooter{
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
#networkLastReward{
|
||||
text-transform: uppercase;
|
||||
}
|
||||
#lastHash{
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
#poolDonations{
|
||||
font-size: 0.75em;
|
||||
}
|
||||
#miningProfitCalc{
|
||||
margin: 35px 0;
|
||||
}
|
||||
#calcHashDropdown{
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
#calcHashHolder{
|
||||
width: 590px;
|
||||
max-width: 100%;
|
||||
}
|
||||
#calcHashRate{
|
||||
z-index: inherit;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
}
|
||||
#calcHashAmount{
|
||||
font-family: 'Inconsolata', monospace;
|
||||
}
|
||||
#calcHashResultsHolder{
|
||||
min-width: 145px;
|
||||
max-width: 145px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#yourStatsInput{
|
||||
z-index: inherit;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
}
|
||||
#yourAddressDisplay > span {
|
||||
font-family: 'Inconsolata', monospace;
|
||||
}
|
||||
#lookUp > span:nth-child(2){
|
||||
display: none;
|
||||
}
|
||||
.yourStats{
|
||||
display: none;
|
||||
}
|
||||
#yourAddressDisplay{
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
#addressError{
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
||||
#payments_rows > tr > td{
|
||||
vertical-align: middle;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 0.95em;
|
||||
text-align: center;
|
||||
}
|
||||
#payments_rows > tr > td:nth-child(2){
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<div id="siteInfo">
|
||||
<!-- Description or information about this pool -->
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 stats">
|
||||
<h3>Network</h3>
|
||||
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="networkHashrate"></span></div>
|
||||
<div><i class="fa fa-clock-o"></i> Block Found: <span id="networkLastBlockFound"></span></div>
|
||||
<div><i class="fa fa-unlock-alt"></i> Difficulty: <span id="networkDifficulty"></span></div>
|
||||
<div><i class="fa fa-bars"></i> Blockchain Height: <span id="blockchainHeight"></span></div>
|
||||
<div><i class="fa fa-money"></i> Last Reward: <span id="networkLastReward"></span></div>
|
||||
<div><i class="fa fa-paw"></i> Last Hash: <a id="lastHash" target="_blank"></a></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 stats">
|
||||
<h3>Our Pool</h3>
|
||||
<div><i class="fa fa-tachometer"></i> Hash Rate: <span id="poolHashrate"></span></div>
|
||||
<div><i class="fa fa-clock-o"></i> Block Found: <span id="poolLastBlockFound"></span></div>
|
||||
<div><i class="fa fa-users"></i> Connected Miners: <span id="poolMiners"></span></div>
|
||||
<div><i class="fa fa-gift"></i> Donations: <span id="poolDonations"></span></div>
|
||||
<div><i class="fa fa-money"></i> Total Pool Fee: <span id="poolFee"></span></div>
|
||||
<div><i class="fa fa-history"></i> Block Found Every: <span id="blockSolvedTime"></span> (est.)</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 stats">
|
||||
<h3 id="marketHeader">Market</h3>
|
||||
<div class="marketFooter">Updated: <span id="marketLastUpdated"></span></div>
|
||||
<div class="marketFooter">Powered by <a href="https://www.cryptonator.com/">Cryptonator</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id="miningProfitCalc">
|
||||
<h3>Estimate Mining Profits</h3>
|
||||
<div id="calcHashHolder">
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="calcHashRate" placeholder="Enter Your Hash Rate">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" id="calcHashDropdown">
|
||||
<span id="calcHashUnit" data-mul="1">KH/s</span> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu" id="calcHashUnits">
|
||||
<li><a href="#" data-mul="0">H/s</a></li>
|
||||
<li><a href="#" data-mul="1">KH/s</a></li>
|
||||
<li><a href="#" data-mul="2">MH/s</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="input-group-addon">=</span>
|
||||
<span class="input-group-addon" id="calcHashResultsHolder"><span id="calcHashAmount"></span> <span id="calcHashSymbol"></span>/day</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="stats">
|
||||
<h3>Your Stats & Payment History</h3>
|
||||
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="yourStatsInput" type="text" placeholder="Enter Your Address">
|
||||
<span class="input-group-btn"><button class="btn btn-default" type="button" id="lookUp">
|
||||
<span><i class="fa fa-search"></i> Lookup</span>
|
||||
<span><i class="fa fa-refresh fa-spin"></i> Searching...</span>
|
||||
</button></span>
|
||||
</div>
|
||||
|
||||
<div id="addressError"></div>
|
||||
<div class="yourStats"><i class="fa fa-key"></i> Address: <span id="yourAddressDisplay"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-bank"></i> Pending Balance: <span id="yourPendingBalance"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-money"></i> Total Paid: <span id="yourPaid"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-clock-o"></i> Last Share Submitted: <span id="yourLastShare"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-tachometer"></i> Hash Rate: <span id="yourHashrateHolder"></span></div>
|
||||
<div class="yourStats"><i class="fa fa-cloud-upload"></i> Total Hashes Submitted: <span id="yourHashes"></span></div>
|
||||
|
||||
<br class="yourStats">
|
||||
|
||||
<h4 class="yourStats">Payments</h4>
|
||||
<div class="yourStats table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="fa fa-clock-o"></i> Time Sent</th>
|
||||
<th><i class="fa fa-paw"></i> Transaction Hash</th>
|
||||
<th><i class="fa fa-money"></i> Amount</th>
|
||||
<th><i class="fa fa-sitemap"></i> Mixin</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="payments_rows">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="yourStats text-center">
|
||||
<button type="button" class="btn btn-default" id="loadMorePayments">Load More</button>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
currentPage = {
|
||||
destroy: function(){
|
||||
$('#networkLastBlockFound,#poolLastBlockFound,#yourLastShare,#marketLastUpdated').timeago('dispose');
|
||||
if (xhrAddressPoll) xhrAddressPoll.abort();
|
||||
if (addressTimeout) clearTimeout(addressTimeout);
|
||||
clearInterval(intervalMarketPolling);
|
||||
for (var marketPoll in xhrMarketGets){
|
||||
xhrMarketGets[marketPoll].abort();
|
||||
}
|
||||
if (xhrGetPayments) xhrGetPayments.abort();
|
||||
},
|
||||
update: function(){
|
||||
|
||||
$('#networkLastBlockFound').timeago('update', new Date(lastStats.network.timestamp * 1000).toISOString());
|
||||
|
||||
updateText('networkHashrate', getReadableHashRateString(lastStats.network.difficulty / 60) + '/sec');
|
||||
updateText('networkDifficulty', lastStats.network.difficulty.toString());
|
||||
updateText('blockchainHeight', lastStats.network.height.toString());
|
||||
updateText('networkLastReward', getReadableCoins(lastStats.network.reward, 4));
|
||||
updateText('lastHash', lastStats.network.hash.substr(0, 13) + '...').setAttribute('href',
|
||||
blockchainExplorer + lastStats.network.hash
|
||||
);
|
||||
|
||||
updateText('poolHashrate', getReadableHashRateString(lastStats.pool.hashrate) + '/sec');
|
||||
|
||||
if (lastStats.pool.lastBlockFound){
|
||||
var d = new Date(parseInt(lastStats.pool.lastBlockFound)).toISOString();
|
||||
$('#poolLastBlockFound').timeago('update', d);
|
||||
}
|
||||
else
|
||||
$('#poolLastBlockFound').removeAttr('title').data('ts', '').update('Never');
|
||||
|
||||
//updateText('poolRoundHashes', lastStats.pool.roundHashes.toString());
|
||||
updateText('poolMiners', lastStats.pool.miners.toString());
|
||||
|
||||
|
||||
var totalFee = lastStats.config.fee;
|
||||
if (lastStats.config.doDonations){
|
||||
totalFee += lastStats.config.donation;
|
||||
totalFee += lastStats.config.coreDonation;
|
||||
var feeText = [];
|
||||
if (lastStats.config.donation > 0) feeText.push(lastStats.config.donation + '% to pool dev');
|
||||
if (lastStats.config.coreDonation > 0) feeText.push(lastStats.config.coreDonation + '% to core devs');
|
||||
updateText('poolDonations', feeText.join(', '));
|
||||
}
|
||||
else{
|
||||
updateText('poolDonations', '');
|
||||
}
|
||||
|
||||
updateText('poolFee', totalFee + '%');
|
||||
|
||||
|
||||
updateText('blockSolvedTime', getReadableTime(lastStats.network.difficulty / lastStats.pool.hashrate));
|
||||
updateText('calcHashSymbol', lastStats.config.symbol);
|
||||
|
||||
calcEstimateProfit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$('#networkLastBlockFound,#poolLastBlockFound,#yourLastShare,#marketLastUpdated').timeago();
|
||||
|
||||
|
||||
function getReadableTime(seconds){
|
||||
|
||||
var units = [ [60, 'second'], [60, 'minute'], [24, 'hour'],
|
||||
[7, 'day'], [4, 'week'], [12, 'month'], [1, 'year'] ];
|
||||
|
||||
function formatAmounts(amount, unit){
|
||||
var rounded = Math.round(amount);
|
||||
return '' + rounded + ' ' + unit + (rounded > 1 ? 's' : '');
|
||||
}
|
||||
|
||||
var amount = seconds;
|
||||
for (var i = 0; i < units.length; i++){
|
||||
if (amount < units[i][0])
|
||||
return formatAmounts(amount, units[i][1]);
|
||||
amount = amount / units[i][0];
|
||||
}
|
||||
return formatAmounts(amount, units[units.length - 1][1]);
|
||||
}
|
||||
|
||||
function getReadableHashRateString(hashrate){
|
||||
var i = 0;
|
||||
var byteUnits = [' H', ' KH', ' MH', ' GH', ' TH', ' PH' ];
|
||||
while (hashrate > 1024){
|
||||
hashrate = hashrate / 1024;
|
||||
i++;
|
||||
}
|
||||
return hashrate.toFixed(2) + byteUnits[i];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Market data polling */
|
||||
|
||||
var intervalMarketPolling = setInterval(updateMarkets, 300000); //poll market data every 5 minutes
|
||||
var xhrMarketGets = {};
|
||||
updateMarkets();
|
||||
function updateMarkets(){
|
||||
var completedFetches = 0;
|
||||
var marketsData = [];
|
||||
for (var i = 0; i < cryptonatorWidget.length; i++){
|
||||
(function(i){
|
||||
xhrMarketGets[cryptonatorWidget[i]] = $.get('https://www.cryptonator.com/api/ticker/' + cryptonatorWidget[i], function(data){
|
||||
marketsData[i] = data;
|
||||
completedFetches++;
|
||||
if (completedFetches !== cryptonatorWidget.length) return;
|
||||
|
||||
var $marketHeader = $('#marketHeader');
|
||||
$('.marketTicker').remove();
|
||||
for (var f = marketsData.length - 1; f >= 0 ; f--){
|
||||
var price = parseFloat(marketsData[f].ticker.price);
|
||||
|
||||
if (price > 1) price = Math.round(price * 100) / 100;
|
||||
else price = marketsData[f].ticker.price;
|
||||
|
||||
$marketHeader.after('<div class="marketTicker">' + marketsData[f].ticker.base + ': <span>' + price + ' ' + marketsData[f].ticker.target + '</span></div>');
|
||||
}
|
||||
$('#marketLastUpdated').timeago('update', new Date(marketsData[0].timestamp * 1000).toISOString());
|
||||
}, 'json');
|
||||
})(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Hash Profitability Calculator */
|
||||
|
||||
$('#calcHashRate').keyup(calcEstimateProfit).change(calcEstimateProfit);
|
||||
|
||||
$('#calcHashUnits > li > a').click(function(e){
|
||||
e.preventDefault();
|
||||
$('#calcHashUnit').text($(this).text()).data('mul', $(this).data('mul'));
|
||||
calcEstimateProfit();
|
||||
});
|
||||
|
||||
function calcEstimateProfit(){
|
||||
try {
|
||||
var rateUnit = Math.pow(1024,parseInt($('#calcHashUnit').data('mul')));
|
||||
var inp2 = parseFloat($('#calcHashRate').val()) * rateUnit;
|
||||
var resl = 16 / ((lastStats.network.difficulty / inp2) / 86400);
|
||||
if (!isNaN(resl)) {
|
||||
updateText('calcHashAmount', (Math.round(resl * 100) / 100).toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch(e){ }
|
||||
updateText('calcHashAmount', '');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Stats by mining address lookup */
|
||||
|
||||
function getPaymentCells(payment){
|
||||
return '<td>' + formatDate(payment.time) + '</td>' +
|
||||
'<td>' + formatPaymentLink(payment.hash) + '</td>' +
|
||||
'<td>' + getReadableCoins(payment.amount, 4, true) + '</td>' +
|
||||
'<td>' + payment.mixin + '</td>';
|
||||
}
|
||||
|
||||
var xhrAddressPoll;
|
||||
var addressTimeout;
|
||||
|
||||
$('#lookUp').click(function(){
|
||||
|
||||
var address = $('#yourStatsInput').val().trim();
|
||||
if (!address){
|
||||
$('#yourStatsInput').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#addressError').hide();
|
||||
$('.yourStats').hide();
|
||||
$('#payments_rows').empty();
|
||||
|
||||
$('#lookUp > span:first-child').hide();
|
||||
$('#lookUp > span:last-child').show();
|
||||
|
||||
|
||||
if (xhrAddressPoll) xhrAddressPoll.abort();
|
||||
if (addressTimeout) clearTimeout(addressTimeout);
|
||||
|
||||
function fetchAddressStats(longpoll){
|
||||
xhrAddressPoll = $.ajax({
|
||||
url: api + '/stats_address',
|
||||
data: {
|
||||
address: address,
|
||||
longpoll: longpoll
|
||||
},
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
success: function(data){
|
||||
|
||||
$('#lookUp > span:last-child').hide();
|
||||
$('#lookUp > span:first-child').show();
|
||||
|
||||
if (!data.stats){
|
||||
$('.yourStats').hide();
|
||||
$('#addressError').text(data.error).show();
|
||||
|
||||
if (addressTimeout) clearTimeout(addressTimeout);
|
||||
addressTimeout = setTimeout(function(){
|
||||
fetchAddressStats(false);
|
||||
}, 2000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$('#addressError').hide();
|
||||
updateText('yourAddressDisplay', address);
|
||||
|
||||
if (data.stats.lastShare)
|
||||
$('#yourLastShare').timeago('update', new Date(parseInt(data.stats.lastShare) * 1000).toISOString());
|
||||
else
|
||||
updateText('yourLastShare', 'Never');
|
||||
|
||||
updateText('yourHashrateHolder', (data.stats.hashrate || '0 H') + '/sec');
|
||||
updateText('yourHashes', (data.stats.hashes || 0).toString());
|
||||
updateText('yourPaid', getReadableCoins(data.stats.paid));
|
||||
updateText('yourPendingBalance', getReadableCoins(data.stats.balance));
|
||||
|
||||
renderPayments(data.payments);
|
||||
|
||||
$('.yourStats').show();
|
||||
|
||||
docCookies.setItem('mining_address', address, Infinity);
|
||||
|
||||
fetchAddressStats(true);
|
||||
|
||||
},
|
||||
error: function(e){
|
||||
if (e.statusText === 'abort') return;
|
||||
$('#lookUp').html(lookupBtnHtml);
|
||||
$('#addressError').text('Connection error').show();
|
||||
|
||||
if (addressTimeout) clearTimeout(addressTimeout);
|
||||
addressTimeout = setTimeout(function(){
|
||||
fetchAddressStats(false);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
fetchAddressStats(false);
|
||||
});
|
||||
|
||||
var address = docCookies.getItem('mining_address');
|
||||
|
||||
if (address){
|
||||
$('#yourStatsInput').val(address);
|
||||
$('#lookUp').click();
|
||||
}
|
||||
|
||||
$('#yourStatsInput').keyup(function(e){
|
||||
if(e.keyCode === 13)
|
||||
$('#lookUp').click();
|
||||
});
|
||||
|
||||
var xhrGetPayments;
|
||||
$('#loadMorePayments').click(function(){
|
||||
if (xhrGetPayments) xhrGetPayments.abort();
|
||||
xhrGetPayments = $.ajax({
|
||||
url: api + '/get_payments',
|
||||
data: {
|
||||
time: $('#payments_rows').children().last().data('time'),
|
||||
address: address
|
||||
},
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
success: function(data){
|
||||
renderPayments(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
|
@ -0,0 +1,93 @@
|
|||
<style>
|
||||
.paymentsStatHolder > span{
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
padding: 1px 9px;
|
||||
border: 1px solid #e5e5e5;
|
||||
margin: 2px;
|
||||
}
|
||||
.paymentsStatHolder > span > span{
|
||||
font-weight: bold;
|
||||
}
|
||||
#payments_rows > tr > td{
|
||||
vertical-align: middle;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 0.95em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="paymentsStatHolder">
|
||||
<span class="bg-primary">Total Payments: <span id="paymentsTotal"></span></span>
|
||||
<span class="bg-info">Total Miners Paid: <span id="paymentsTotalPaid"></span></span>
|
||||
<span class="bg-info">Minimum Payment Threshold: <span id="paymentsMinimum"></span></span>
|
||||
<span class="bg-info">Denomination Unit: <span id="paymentsDenomination"></span></span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="fa fa-clock-o"></i> Time Sent</th>
|
||||
<th><i class="fa fa-paw"></i> Transaction Hash</th>
|
||||
<th><i class="fa fa-money"></i> Amount</th>
|
||||
<th><i class="fa fa-tag"></i> Fee</th>
|
||||
<th><i class="fa fa-sitemap"></i> Mixin</th>
|
||||
<th><i class="fa fa-group"></i> Payees</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="payments_rows">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-center">
|
||||
<button type="button" class="btn btn-default" id="loadMorePayments">Load More</button>
|
||||
</p>
|
||||
|
||||
|
||||
<script>
|
||||
currentPage = {
|
||||
destroy: function(){
|
||||
if (xhrGetPayments) xhrGetPayments.abort();
|
||||
},
|
||||
update: function(){
|
||||
updateText('paymentsTotal', lastStats.pool.totalPayments.toString());
|
||||
updateText('paymentsTotalPaid', lastStats.pool.totalMinersPaid.toString());
|
||||
updateText('paymentsMinimum', getReadableCoins(lastStats.config.minPaymentThreshold, 3));
|
||||
updateText('paymentsDenomination', getReadableCoins(lastStats.config.denominationUnit, 3));
|
||||
renderPayments(lastStats.pool.payments);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var xhrGetPayments;
|
||||
$('#loadMorePayments').click(function(){
|
||||
if (xhrGetPayments) xhrGetPayments.abort();
|
||||
xhrGetPayments = $.ajax({
|
||||
url: api + '/get_payments',
|
||||
data: {
|
||||
time: $('#payments_rows').children().last().data('time')
|
||||
},
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
success: function(data){
|
||||
renderPayments(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function getPaymentCells(payment){
|
||||
return '<td>' + formatDate(payment.time) + '</td>' +
|
||||
'<td>' + formatPaymentLink(payment.hash) + '</td>' +
|
||||
'<td>' + getReadableCoins(payment.amount, 4, true) + '</td>' +
|
||||
'<td>' + getReadableCoins(payment.fee, 4, true) + '</td>' +
|
||||
'<td>' + payment.mixin + '</td>' +
|
||||
'<td>' + payment.recipients + '</td>';
|
||||
}
|
||||
|
||||
</script>
|
|
@ -0,0 +1,199 @@
|
|||
<style>
|
||||
.blocksStatHolder > span{
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
padding: 1px 9px;
|
||||
border: 1px solid #e5e5e5;
|
||||
margin: 2px;
|
||||
}
|
||||
.blocksStatHolder > span > span{
|
||||
font-weight: bold;
|
||||
}
|
||||
#blocks_rows > tr > td{
|
||||
vertical-align: middle;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
font-size: 0.95em;
|
||||
text-align: center;
|
||||
}
|
||||
.luckGood{
|
||||
color: darkgreen;
|
||||
}
|
||||
.luckBad{
|
||||
color: darkred;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="blocksStatHolder">
|
||||
<span class="bg-primary">Total Blocks Mined: <span id="blocksTotal"></span></span>
|
||||
<span class="bg-info">Maturity Depth Requirement: <span id="blocksMaturityCount"></span></span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="fa fa-bars"></i> Height</th>
|
||||
<th title="How many more blocks network must mine before this block is matured"><i class="fa fa-link"></i> Maturity</th>
|
||||
<th><i class="fa fa-unlock-alt"></i> Difficulty</th>
|
||||
<th><i class="fa fa-paw"></i> Block Hash</th>
|
||||
<th><i class="fa fa-clock-o"></i> Time Found</th>
|
||||
<th><i class="fa fa-star-half-o"></i> Luck</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="blocks_rows">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-center">
|
||||
<button type="button" class="btn btn-default" id="loadMoreBlocks">Load More</button>
|
||||
</p>
|
||||
|
||||
<script>
|
||||
|
||||
currentPage = {
|
||||
destroy: function(){
|
||||
if (xhrGetBlocks) xhrGetBlocks.abort();
|
||||
},
|
||||
update: function(){
|
||||
updateText('blocksTotal', lastStats.pool.totalBlocks.toString());
|
||||
updateText('blocksMaturityCount', lastStats.config.depth.toString());
|
||||
renderBlocks(lastStats.pool.blocks);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
var xhrGetBlocks;
|
||||
$('#loadMoreBlocks').click(function(){
|
||||
if (xhrGetBlocks) xhrGetBlocks.abort();
|
||||
xhrGetBlocks = $.ajax({
|
||||
url: api + '/get_blocks',
|
||||
data: {
|
||||
height: $('#blocks_rows').children().last().data('height')
|
||||
},
|
||||
dataType: 'json',
|
||||
cache: 'false',
|
||||
success: function(data){
|
||||
renderBlocks(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function parseBlock(height, serializedBlock){
|
||||
var parts = serializedBlock.split(':');
|
||||
var block = {
|
||||
height: parseInt(height),
|
||||
hash: parts[0],
|
||||
time: parts[1],
|
||||
difficulty: parseInt(parts[2]),
|
||||
shares: parseInt(parts[3]),
|
||||
orphaned: parts[4],
|
||||
reward: parts[5]
|
||||
};
|
||||
|
||||
var toGo = lastStats.config.depth - (lastStats.network.height - block.height);
|
||||
block.maturity = toGo < 1 ? '' : (toGo + ' to go');
|
||||
|
||||
switch (block.orphaned){
|
||||
case '0':
|
||||
block.status = 'unlocked';
|
||||
break;
|
||||
case '1':
|
||||
block.status = 'orphaned';
|
||||
break;
|
||||
default:
|
||||
block.status = 'pending';
|
||||
break;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getBlockRowElement(block, jsonString){
|
||||
|
||||
function formatLuck(difficulty, shares){
|
||||
if (difficulty > shares){
|
||||
var percent = 100 - Math.round(shares / difficulty * 100);
|
||||
return '<span class="luckGood">' + percent + '%</span>';
|
||||
}
|
||||
else{
|
||||
var percent = (100 - Math.round(difficulty / shares * 100)) * -1;
|
||||
return '<span class="luckBad">' + percent + '%</span>';
|
||||
}
|
||||
}
|
||||
|
||||
function formatBlockLink(hash){
|
||||
return '<a target="_blank" href="' + blockchainExplorer + hash + '">' + hash + '</a>';
|
||||
}
|
||||
|
||||
var blockStatusClasses = {
|
||||
'pending': '',
|
||||
'unlocked': 'success',
|
||||
'orphaned': 'danger'
|
||||
};
|
||||
|
||||
var row = document.createElement('tr');
|
||||
row.setAttribute('data-json', jsonString);
|
||||
row.setAttribute('data-height', block.height);
|
||||
row.setAttribute('id', 'blockRow' + block.height);
|
||||
row.setAttribute('title', block.status);
|
||||
row.className = blockStatusClasses[block.status];
|
||||
|
||||
var columns =
|
||||
'<td>' + block.height + '</td>' +
|
||||
'<td>' + block.maturity + '</td>' +
|
||||
'<td>' + block.difficulty + '</td>' +
|
||||
'<td>' + formatBlockLink(block.hash) + '</td>' +
|
||||
'<td>' + formatDate(block.time) + '</td>' +
|
||||
'<td>' + formatLuck(block.difficulty, block.shares) + '</td>';
|
||||
|
||||
row.innerHTML = columns;
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
function renderBlocks(blocksResults){
|
||||
|
||||
var $blocksRows = $('#blocks_rows');
|
||||
|
||||
for (var i = 0; i < blocksResults.length; i += 2){
|
||||
|
||||
var block = parseBlock(blocksResults[i + 1], blocksResults[i]);
|
||||
|
||||
var blockJson = JSON.stringify(block);
|
||||
|
||||
var existingRow = document.getElementById('blockRow' + block.height);
|
||||
|
||||
if (existingRow && existingRow.getAttribute('data-json') !== blockJson){
|
||||
$(existingRow).replaceWith(getBlockRowElement(block, blockJson));
|
||||
}
|
||||
else if (!existingRow){
|
||||
|
||||
var blockElement = getBlockRowElement(block, blockJson);
|
||||
|
||||
var inserted = false;
|
||||
var rows = $blocksRows.children().get();
|
||||
for (var f = 0; f < rows.length; f++) {
|
||||
var bHeight = parseInt(rows[f].getAttribute('data-height'));
|
||||
if (bHeight < block.height){
|
||||
inserted = true;
|
||||
$(rows[f]).before(blockElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted)
|
||||
$blocksRows.append(blockElement);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -0,0 +1,24 @@
|
|||
<h3>Contact</h3>
|
||||
<p>Email pool support at <a id="emailLink" href=""></a></p>
|
||||
|
||||
<h3>Chat Room</h3>
|
||||
<iframe id="kiwi_irc" style="border:0; width:100%; height:500px;"></iframe>
|
||||
|
||||
<script>
|
||||
|
||||
currentPage = {
|
||||
destroy: function(){
|
||||
|
||||
},
|
||||
update: function(){
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('kiwi_irc').setAttribute('src', 'https://kiwiirc.com/client/' + irc);
|
||||
|
||||
var emailEl = document.getElementById('emailLink');
|
||||
emailEl.setAttribute('href', 'mailto:' + email);
|
||||
emailEl.textContent = email;
|
||||
|
||||
</script>
|
Loading…
Reference in New Issue