diff --git a/.gitignore b/.gitignore index cc96762a1..93d97efb7 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ settings.json *.ico *.png + +.DS_Store + +package-lock.json diff --git a/CHANGELOG b/CHANGELOG index b2a22aecf..592dd55fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +1.6.2 +* Indexing is now MUCH faster. +* Added AddressTX model/collection - REQUIRES REINDEX +* Added balance history to Address TX History +* Added support for AJAX loading of Address TX History, using txcount setting for loading +* Stopped maximum TX count trimming TX's in AddressTX collection +* Removed tx_array from Address collection -> AddressTX +* Fix mismatching balances/sent/received, negative balances etc +* Added new file lock during database indexing: tmp/db_index.pid +* New setting to lock during indexing: lock_during_index +* Add setting to call bitcoin-core directly or use RPC during indexing + 1.6.1 * fixed last_txs setting * added hashrate_units setting diff --git a/README.md b/README.md index ba9e13486..7b40dd26a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Iquidus Explorer - 1.6.1 +Iquidus Explorer - 1.6.2 ================ An open source block explorer written in node.js. @@ -6,19 +6,20 @@ An open source block explorer written in node.js. ### See it in action * [Deutsche eMark](http://b.emark.tk/) -* [Sphere](http://sphere.iquidus.io) * [Vertcoin](http://explorer.vertcoin.info/) -* [Vivo](http://vivo.explorerz.top:3003) +* [TheHolyRogerCoin (ROGER) Explorer](https://explorer.theholyroger.com/) +* [CPUChain (CPU) Explorer](https://explorer.cpuchain.org/) +* [Omega Blockchain Explorer](http://explorer.omegablockchain.net/) +* [Sugarchain Explorer](https://1explorer.sugarchain.org/) * [Florincoin](https://florincoin.info/info) * [Maxcoin Explorer 1](https://explorer.maxcoinproject.net/) -* [Maxcoin Explorer 2](https://explorer2.maxcoinproject.net/) -*note: If you would like your instance mentioned here contact me* +*Note: If you would like your instance mentioned here contact me* ### Requires -* node.js >= 0.10.28 +* node.js >= 0.10.28 (8.17.0 is advised for updated dependencies) * mongodb 2.6.x * *coind @@ -36,7 +37,7 @@ Create user with read/write access: > db.createUser( { user: "iquidus", pwd: "3xp!0reR", roles: [ "readWrite" ] } ) -*note: If you're using mongo shell 2.4.x, use the following to create your user: +*Note: If you're using mongo shell 2.4.x, use the following to create your user: > db.addUser( { user: "username", pwd: "password", roles: [ "readWrite"] }) @@ -58,7 +59,7 @@ Create user with read/write access: npm start -*note: mongod must be running to start the explorer* +*Note: mongod must be running to start the explorer* As of version 1.4.0 the explorer defaults to cluster mode, forking an instance of its process to each cpu core. This results in increased performance and stability. Load balancing gets automatically taken care of and any instances that for some reason die, will be restarted automatically. For testing/development (or if you just wish to) a single instance can be launched with @@ -102,22 +103,22 @@ sync.js (located in scripts/) is used for updating the local databases. This scr ### Wallet -Iquidus Explorer is intended to be generic so it can be used with any wallet following the usual standards. The wallet must be running with atleast the following flags +Iquidus Explorer is intended to be generic, so it can be used with any wallet following the usual standards. The wallet must be running with atleast the following flags -daemon -txindex + +### Security -### Donate - - BTC: 168hdKA3fkccPtkxnX8hBrsxNubvk4udJi - JBS: JZp9893FMmrm1681bDuJBU7c6w11kyEY7D +Ensure mongodb is not exposed to the outside world via your mongo config or a firewall to prevent outside tampering of the indexed chain data. ### Known Issues **script is already running.** -If you receive this message when launching the sync script either a) a sync is currently in progress, or b) a previous sync was killed before it completed. If you are certian a sync is not in progress remove the index.pid from the tmp folder in the explorer root directory. +If you receive this message when launching the sync script either a) a sync is currently in progress, or b) a previous sync was killed before it completed. If you are certian a sync is not in progress remove the index.pid and db_index.pid from the tmp folder in the explorer root directory. rm tmp/index.pid + rm tmp/db_index.pid **exceeding stack size** diff --git a/UPGRADE b/UPGRADE index af8eb0d45..77737283b 100644 --- a/UPGRADE +++ b/UPGRADE @@ -1,5 +1,14 @@ Note: All updates require the explorer to be restarted +1.6.1 -> 1.6.2 +* remove tmp/db_index.pid (if it exists) +* Add new settings to settings.json (see settings.json.template) + * lock_during_index + * use_rpc + * txcount_per_page + * index.txs_per_page +* Reindex explorerdb (node --stack-size=15000 scripts/sync.js index reindex) to build new address TX collection + 1.6.0 -> 1.6.1 * Add new cryptsy_id and hashrate_units settings (see settings.json.template) * remove tmp/market.pid (if it exists) diff --git a/app.js b/app.js index 81dd91e5b..84099c7b9 100644 --- a/app.js +++ b/app.js @@ -44,7 +44,7 @@ app.set('view engine', 'jade'); app.use(favicon(path.join(__dirname, settings.favicon))); app.use(logger('dev')); app.use(bodyParser.json()); -app.use(bodyParser.urlencoded()); +app.use(bodyParser.urlencoded({ extended: true })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); @@ -58,28 +58,77 @@ app.use('/ext/getmoneysupply', function(req,res){ }); app.use('/ext/getaddress/:hash', function(req,res){ - db.get_address(req.param('hash'), function(address){ + db.get_address(req.params.hash, function(address){ if (address) { var a_ext = { address: address.a_id, sent: (address.sent / 100000000), received: (address.received / 100000000), balance: (address.balance / 100000000).toString().replace(/(^-+)/mg, ''), - last_txs: address.txs, }; res.send(a_ext); } else { - res.send({ error: 'address not found.', hash: req.param('hash')}) + res.send({ error: 'address not found.', hash: req.params.hash}) + } + }); +}); + +app.use('/ext/gettx/:txid', function(req, res) { + var txid = req.params.txid; + db.get_tx(txid, function(tx) { + if (tx) { + lib.get_blockcount(function(blockcount) { + res.send({ active: 'tx', tx: tx, confirmations: settings.confirmations, blockcount: blockcount}); + }); + } + else { + lib.get_rawtransaction(txid, function(rtx) { + if (rtx.txid) { + lib.prepare_vin(rtx, function(vin) { + lib.prepare_vout(rtx.vout, rtx.txid, vin, function(rvout, rvin) { + lib.calculate_total(rvout, function(total){ + if (!rtx.confirmations > 0) { + var utx = { + txid: rtx.txid, + vin: rvin, + vout: rvout, + total: total.toFixed(8), + timestamp: rtx.time, + blockhash: '-', + blockindex: -1, + }; + res.send({ active: 'tx', tx: utx, confirmations: settings.confirmations, blockcount:-1}); + } else { + var utx = { + txid: rtx.txid, + vin: rvin, + vout: rvout, + total: total.toFixed(8), + timestamp: rtx.time, + blockhash: rtx.blockhash, + blockindex: rtx.blockheight, + }; + lib.get_blockcount(function(blockcount) { + res.send({ active: 'tx', tx: utx, confirmations: settings.confirmations, blockcount: blockcount}); + }); + } + }); + }); + }); + } else { + res.send({ error: 'tx not found.', hash: txid}); + } + }); } }); }); app.use('/ext/getbalance/:hash', function(req,res){ - db.get_address(req.param('hash'), function(address){ + db.get_address(req.params.hash, function(address){ if (address) { res.send((address.balance / 100000000).toString().replace(/(^-+)/mg, '')); } else { - res.send({ error: 'address not found.', hash: req.param('hash')}) + res.send({ error: 'address not found.', hash: req.params.hash}) } }); }); @@ -94,12 +143,75 @@ app.use('/ext/getdistribution', function(req,res){ }); }); -app.use('/ext/getlasttxs/:min', function(req,res){ - db.get_last_txs(settings.index.last_txs, (req.params.min * 100000000), function(txs){ - res.send({data: txs}); +app.use('/ext/getlasttxsajax/:min', function(req,res){ + if(typeof req.query.length === 'undefined' || isNaN(req.query.length) || req.query.length > settings.index.last_txs){ + req.query.length = settings.index.last_txs; + } + if(typeof req.query.start === 'undefined' || isNaN(req.query.start) || req.query.start < 0){ + req.query.start = 0; + } + if(typeof req.params.min === 'undefined' || isNaN(req.params.min ) || req.params.min < 0){ + req.params.min = 0; + } else { + req.params.min = (req.params.min * 100000000); + } + db.get_last_txs_ajax(req.query.start, req.query.length, req.params.min,function(txs, count){ + var data = []; + for(i=0; i settings.txcount){ + req.query.length = settings.txcount; + } + if(isNaN(req.query.start) || req.query.start < 0){ + req.query.start = 0; + } + db.get_address_txs_ajax(req.params.address, req.query.start, req.query.length,function(txs, count){ + var data = []; + for(i=0; i settings.txcount ) { - tx_array.shift(); - } - Address.update({a_id:hash}, { - txs: tx_array, - received: received, - sent: sent, - balance: received - sent - }, function() { - return cb(); + Address.updateOne({a_id:hash}, { + received: received, + sent: sent, + balance: received - sent + }, function() { + // ensure tx doesnt already exist in address.txs + find_address_tx(hash, txid, function(address_tx) { + if (typeof address_tx == "undefined") { + var newAddressTx = new AddressTx({ + a_id: hash, + balance: received - sent, + txid: txid + }); + newAddressTx.save(function(err) { + if (err) { + return cb(err); + } else { + return cb(); + } + }); + } else { + AddressTx.updateOne({a_id: hash, txid: txid}, { + a_id: hash, + balance: received - sent, + txid: txid + }, function() { + return cb(); + }); + } }); - } else { - if (type == tx_array[index].type) { - return cb(); //duplicate - } else { - Address.update({a_id:hash}, { - txs: tx_array, - received: received, - sent: sent, - balance: received - sent - }, function() { - return cb(); - }); - } - } - }); - } + }); + } } else { //new address if (type == 'vin') { var newAddress = new Address({ a_id: hash, - txs: [ {addresses: txid, type: 'vin'} ], sent: amount, balance: amount, }); } else { var newAddress = new Address({ a_id: hash, - txs: [ {addresses: txid, type: 'vout'} ], received: amount, balance: amount, }); @@ -112,9 +129,18 @@ function update_address(hash, txid, amount, type, cb) { if (err) { return cb(err); } else { - //console.log('address saved: %s', hash); - //console.log(newAddress); - return cb(); + var newAddressTx = new AddressTx({ + a_id: hash, + balance: amount, + txid: txid + }); + newAddressTx.save(function(err) { + if (err) { + return cb(err); + } else { + return cb(); + } + }); } }); } @@ -131,56 +157,50 @@ function find_tx(txid, cb) { }); } -function save_tx(txid, cb) { +function save_tx(txid, blockheight, cb) { //var s_timer = new Date().getTime(); lib.get_rawtransaction(txid, function(tx){ if (tx != 'There was an error. Check your console.') { - lib.get_block(tx.blockhash, function(block){ - if (block) { - lib.prepare_vin(tx, function(vin) { - lib.prepare_vout(tx.vout, txid, vin, function(vout, nvin) { - lib.syncLoop(vin.length, function (loop) { - var i = loop.iteration(); - update_address(nvin[i].addresses, txid, nvin[i].amount, 'vin', function(){ - loop.next(); + lib.prepare_vin(tx, function(vin) { + lib.prepare_vout(tx.vout, txid, vin, function(vout, nvin) { + lib.syncLoop(vin.length, function (loop) { + var i = loop.iteration(); + update_address(nvin[i].addresses, txid, nvin[i].amount, 'vin', function(){ + loop.next(); + }); + }, function(){ + lib.syncLoop(vout.length, function (subloop) { + var t = subloop.iteration(); + if (vout[t].addresses) { + update_address(vout[t].addresses, txid, vout[t].amount, 'vout', function(){ + subloop.next(); }); - }, function(){ - lib.syncLoop(vout.length, function (subloop) { - var t = subloop.iteration(); - if (vout[t].addresses) { - update_address(vout[t].addresses, txid, vout[t].amount, 'vout', function(){ - subloop.next(); - }); + } else { + subloop.next(); + } + }, function(){ + lib.calculate_total(vout, function(total){ + var newTx = new Tx({ + txid: tx.txid, + vin: nvin, + vout: vout, + total: total.toFixed(8), + timestamp: tx.time, + blockhash: tx.blockhash, + blockindex: blockheight, + }); + newTx.save(function(err) { + if (err) { + return cb(err); } else { - subloop.next(); + //console.log('txid: '); + return cb(); } - }, function(){ - lib.calculate_total(vout, function(total){ - var newTx = new Tx({ - txid: tx.txid, - vin: nvin, - vout: vout, - total: total.toFixed(8), - timestamp: tx.time, - blockhash: tx.blockhash, - blockindex: block.height, - }); - newTx.save(function(err) { - if (err) { - return cb(err); - } else { - //console.log('txid: '); - return cb(); - } - }); - }); }); }); }); }); - } else { - return cb('block not found: ' + tx.blockhash); - } + }); }); } else { return cb('tx not found: ' + txid); @@ -190,6 +210,11 @@ function save_tx(txid, cb) { function get_market_data(market, cb) { switch(market) { + case 'altmarkets': + altmarkets.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){ + return cb(err, obj); + }); + break; case 'bittrex': bittrex.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){ return cb(err, obj); @@ -230,11 +255,68 @@ function get_market_data(market, cb) { return cb(err, obj); }); break; + case 'crex': + crex.get_data(settings.markets.coin, settings.markets.exchange, function (err, obj){ + return cb(err, obj); + }); + break; + case 'tradesatoshi': + tradesatoshi.get_data(settings.markets.coin, settings.markets.exchange, function (err, obj){ + return cb(err, obj); + }); + break; default: return cb(null); } } +function create_lock(lockfile, cb) { + if (settings.lock_during_index == true) { + var fname = './tmp/' + lockfile + '.pid'; + fs.appendFile(fname, process.pid, function (err) { + if (err) { + console.log("Error: unable to create %s", fname); + process.exit(1); + } else { + return cb(); + } + }); + } else { + return cb(); + } +} + +function remove_lock(lockfile, cb) { + if (settings.lock_during_index == true) { + var fname = './tmp/' + lockfile + '.pid'; + fs.unlink(fname, function (err){ + if(err) { + console.log("unable to remove lock: %s", fname); + process.exit(1); + } else { + return cb(); + } + }); + } else { + return cb(); + } +} + +function is_locked(lockfile, cb) { + if (settings.lock_during_index == true) { + var fname = './tmp/' + lockfile + '.pid'; + fs.exists(fname, function (exists){ + if(exists) { + return cb(true); + } else { + return cb(false); + } + }); + } else { + return cb(false); + } +} + module.exports = { // initialize DB connect: function(database, cb) { @@ -250,6 +332,16 @@ module.exports = { }); }, + is_locked: function(cb) { + is_locked("db_index", function (exists) { + if (exists) { + return cb(true); + } else { + return cb(false); + } + }); + }, + check_stats: function(coin, cb) { Stats.findOne({coin: coin}, function(err, stats) { if(stats) { @@ -301,16 +393,16 @@ module.exports = { //property: 'received' or 'balance' update_richlist: function(list, cb){ if(list == 'received') { - Address.find({}).sort({received: 'desc'}).limit(100).exec(function(err, addresses){ - Richlist.update({coin: settings.coin}, { + Address.find({}, 'a_id balance received').sort({received: 'desc'}).limit(100).exec(function(err, addresses){ + Richlist.updateOne({coin: settings.coin}, { received: addresses, }, function() { return cb(); }); }); } else { //balance - Address.find({}).sort({balance: 'desc'}).limit(100).exec(function(err, addresses){ - Richlist.update({coin: settings.coin}, { + Address.find({}, 'a_id balance received').sort({balance: 'desc'}).limit(100).exec(function(err, addresses){ + Richlist.updateOne({coin: settings.coin}, { balance: addresses, }, function() { return cb(); @@ -342,41 +434,92 @@ module.exports = { }); }, - create_tx: function(txid, cb) { - save_tx(txid, function(err){ - if (err) { - return cb(err); - } else { - //console.log('tx stored: %s', txid); + create_txs: function(block, cb) { + is_locked("db_index", function (exists) { + if (exists) { + console.log("db_index lock file exists..."); return cb(); + } else { + lib.syncLoop(block.tx.length, function (loop) { + var i = loop.iteration(); + save_tx(block.tx[i], block.height, function(err){ + if (err) { + loop.next(); + } else { + //console.log('tx stored: %s', block.tx[i]); + loop.next(); + } + }); + }, function(){ + return cb(); + }); } }); }, - create_txs: function(block, cb) { - lib.syncLoop(block.tx.length, function (loop) { - var i = loop.iteration(); - save_tx(block.tx[i], function(err){ - if (err) { - loop.next(); - } else { - //console.log('tx stored: %s', block.tx[i]); - loop.next(); - } - }); - }, function(){ - return cb(); + get_last_txs_ajax: function(start, length, min, cb) { + Tx.countDocuments({'total': {$gt: min}}, function(err, count){ + Tx.find({'total': {$gt: min}}).sort({blockindex: 'desc'}).skip(Number(start)).limit(Number(length)).exec(function(err, txs){ + if (err) { + return cb(err); + } else { + return cb(txs, count); + } + }); }); }, - get_last_txs: function(count, min, cb) { - Tx.find({'total': {$gt: min}}).sort({_id: 'desc'}).limit(count).exec(function(err, txs){ - if (err) { + get_address_txs_ajax: function(hash, start, length, cb) { + var totalCount = 0; + Address.findOne({a_id: hash}, function(err, addressTotalTxs) { + + if(err) { return cb(err); } else { - return cb(txs); + AddressTx.find({a_id: hash}).count({}, function(err, count){ + if(err) { + return cb(err); + } else { + totalCount = count; + AddressTx.find({a_id: hash}).sort({_id: 'desc'}).skip(Number(start)).limit(Number(length)).exec(function (err, address) { + if (err) { + return cb(err); + } else { + var txs = []; + var count = address.length; + var hashes = address; + + var txs = []; + + lib.syncLoop(count, function (loop) { + var i = loop.iteration(); + find_tx(hashes[i].txid, function (tx) { + if (tx && !txs.includes(tx)) { + // tx = {...hashes[i], ...tx} + tx.balance = hashes[i].balance; + txs.push(tx); + loop.next(); + } else if (!txs.includes(tx)) { + // tx = {...hashes[i], ...tx} + tx.balance = hashes[i].balance; + txs.push("1. Not found"); + loop.next(); + } else { + loop.next(); + } + }) + }, function () { + return cb(txs, totalCount); + }); + } + }); + } + }); + } }); + + }, create_market: function(coin, exchange, market, cb) { @@ -436,6 +579,17 @@ module.exports = { } }); }, + + // drops richlist data for given coin + delete_richlist: function(coin, cb) { + Richlist.findOneAndRemove({coin: coin}, function(err, exists) { + if(exists) { + return cb(true); + } else { + return cb(false); + } + }); + }, // checks richlist data exists for given coin check_richlist: function(coin, cb) { Richlist.findOne({coin: coin}, function(err, exists) { @@ -548,7 +702,7 @@ module.exports = { }); }, function(){ console.log(newVotes); - Heavy.update({coin: coin}, { + Heavy.updateOne({coin: coin}, { lvote: vote, reward: reward, supply: supply, @@ -577,7 +731,7 @@ module.exports = { update_markets_db: function(market, cb) { get_market_data(market, function (err, obj) { if (err == null) { - Markets.update({market:market}, { + Markets.updateOne({market:market}, { chartdata: JSON.stringify(obj.chartdata), buys: obj.buys, sells: obj.sells, @@ -585,7 +739,7 @@ module.exports = { summary: obj.stats, }, function() { if ( market == settings.markets.default ) { - Stats.update({coin:settings.coin}, { + Stats.updateOne({coin:settings.coin}, { last_price: obj.stats.last, }, function(){ return cb(null); @@ -609,7 +763,7 @@ module.exports = { } lib.get_supply( function (supply){ lib.get_connectioncount(function (connections) { - Stats.update({coin: coin}, { + Stats.updateOne({coin: coin}, { coin: coin, count : count, supply: supply, @@ -624,64 +778,75 @@ module.exports = { // updates tx, address & richlist db's; called by sync.js update_tx_db: function(coin, start, end, timeout, cb) { - var complete = false; - lib.syncLoop((end - start) + 1, function (loop) { - var x = loop.iteration(); - if (x % 5000 === 0) { - Tx.find({}).where('blockindex').lt(start + x).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){ - Stats.update({coin: coin}, { - last: start + x - 1, - last_txs: '' //not used anymore left to clear out existing objects - }, function() {}); - }); - } - lib.get_blockhash(start + x, function(blockhash){ - if (blockhash) { - lib.get_block(blockhash, function(block) { - if (block) { - lib.syncLoop(block.tx.length, function (subloop) { - var i = subloop.iteration(); - Tx.findOne({txid: block.tx[i]}, function(err, tx) { - if(tx) { - tx = null; - subloop.next(); - } else { - save_tx(block.tx[i], function(err){ - if (err) { - console.log(err); - } else { - console.log('%s: %s', block.height, block.tx[i]); - } - setTimeout( function(){ - tx = null; - subloop.next(); - }, timeout); + is_locked("db_index", function (exists) { + if (exists) { + console.log("db_index lock file exists..."); + return cb(); + } else { + create_lock("db_index", function (){ + var complete = false; + lib.syncLoop((end - start) + 1, function (loop) { + var x = loop.iteration(); + if (x % 5000 === 0) { + Tx.find({}).where('blockindex').lt(start + x).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){ + Stats.updateOne({coin: coin}, { + last: start + x - 1, + last_txs: '' //not used anymore left to clear out existing objects + }, function() {}); + }); + } + lib.get_blockhash(start + x, function(blockhash){ + if (blockhash) { + lib.get_block(blockhash, function(block) { + if (block) { + lib.syncLoop(block.tx.length, function (subloop) { + var i = subloop.iteration(); + Tx.findOne({txid: block.tx[i]}, function(err, tx) { + if(tx) { + tx = null; + subloop.next(); + } else { + save_tx(block.tx[i], block.height, function(err){ + if (err) { + console.log(err); + } else { + console.log('%s: %s', block.height, block.tx[i]); + } + setTimeout( function(){ + tx = null; + subloop.next(); + }, timeout); + }); + } + }); + }, function(){ + blockhash = null; + block = null; + loop.next(); }); + } else { + console.log('block not found: %s', blockhash); + loop.next(); } }); - }, function(){ - blockhash = null; - block = null; + } else { loop.next(); + } + }); + }, function(){ + Tx.find({}).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){ + Stats.updateOne({coin: coin}, { + last: end, + last_txs: '' //not used anymore left to clear out existing objects + }, function() { + remove_lock("db_index", function(){ + return cb(); + }); }); - } else { - console.log('block not found: %s', blockhash); - loop.next(); - } + }); }); - } else { - loop.next(); - } - }); - }, function(){ - Tx.find({}).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){ - Stats.update({coin: coin}, { - last: end, - last_txs: '' //not used anymore left to clear out existing objects - }, function() { - return cb(); }); - }); + } }); }, @@ -711,6 +876,28 @@ module.exports = { }) }, + drop_peer: function(address, cb) { + Peers.deleteOne({address: address}, function(err) { + if (err) { + console.log(err); + return cb(); + } else { + return cb () + } + }) + }, + + drop_peers: function(cb) { + Peers.deleteMany({}, function(err) { + if (err) { + console.log(err); + return cb(); + } else { + return cb () + } + }) + }, + get_peers: function(cb) { Peers.find({}, function(err, peers) { if (err) { diff --git a/lib/explorer.js b/lib/explorer.js index 56908e2a6..5c4662db1 100644 --- a/lib/explorer.js +++ b/lib/explorer.js @@ -4,6 +4,9 @@ var request = require('request') var base_url = 'http://127.0.0.1:' + settings.port + '/api/'; +const Client = require('bitcoin-core'); +const client = new Client(settings.wallet); + // returns coinbase total sent as current coin supply function coinbase_supply(cb) { @@ -95,24 +98,60 @@ module.exports = { }, get_blockhash: function(height, cb) { - var uri = base_url + 'getblockhash?height=' + height; - request({uri: uri, json: true}, function (error, response, body) { - return cb(body); - }); + if (settings.use_rpc) { + var uri = base_url + 'getblockhash?height=' + height; + request({uri: uri, json: true}, function (error, response, body) { + return cb(body); + }); + } else { + client.command([{method:'getblockhash', parameters: [parseInt(height)]}], function(err, response){ + if(err){console.log('Error: ', err); } + else{ + if(response[0].name == 'RpcError'){ + return cb('There was an error. Check your console.'); + } + return cb(response[0]); + } + }); + } }, get_block: function(hash, cb) { - var uri = base_url + 'getblock?hash=' + hash; - request({uri: uri, json: true}, function (error, response, body) { - return cb(body); - }); + if (settings.use_rpc) { + var uri = base_url + 'getblock?hash=' + hash; + request({uri: uri, json: true}, function (error, response, body) { + return cb(body); + }); + } else { + client.command([{method:'getblock', parameters: [hash]}], function(err, response){ + if(err){console.log('Error: ', err); } + else{ + if(response[0].name == 'RpcError'){ + return cb('There was an error. Check your console.'); + } + return cb(response[0]); + } + }); + } }, get_rawtransaction: function(hash, cb) { - var uri = base_url + 'getrawtransaction?txid=' + hash + '&decrypt=1'; - request({uri: uri, json: true}, function (error, response, body) { - return cb(body); - }); + if (settings.use_rpc) { + var uri = base_url + 'getrawtransaction?txid=' + hash + '&decrypt=1'; + request({uri: uri, json: true}, function (error, response, body) { + return cb(body); + }); + } else { + client.command([{method:'getrawtransaction', parameters: [hash, 1]}], function(err, response){ + if(err){console.log('Error: ', err); } + else{ + if(response[0].name == 'RpcError'){ + return cb('There was an error. Check your console.'); + } + return cb(response[0]); + } + }); + } }, get_maxmoney: function(cb) { diff --git a/lib/locale.js b/lib/locale.js index 4cdb5333a..41701c553 100644 --- a/lib/locale.js +++ b/lib/locale.js @@ -59,6 +59,7 @@ exports.initial_index_alert = "Indexing is currently incomplete, functionality i exports.a_menu_showing = "Showing", exports.a_menu_txs = "transactions", exports.a_menu_all = "All", +exports.a_qr = "QR Code", exports.rl_received_coins = "Top 100 - Received Coins", exports.rl_current_balance = "Top 100 - Current Balance", @@ -71,6 +72,7 @@ exports.rl_top75 = "Top 51-75", exports.rl_top100 = "Top 76-100", exports.rl_hundredplus = "101+", +exports.net_addnodes = "Add Nodes", exports.net_connections = "Connections", exports.net_address = "Address", exports.net_protocol = "Protocol", @@ -136,12 +138,15 @@ exports.heavy_lastxvotes = "Last 20 votes", exports.poloniex = "Poloniex", exports.bittrex = "Bittrex", +exports.altmarkets = "AltMarkets", exports.bleutrade = "Bleutrade", exports.yobit = "Yobit", exports.cryptsy = "Cryptsy", exports.cryptopia = "Cryptopia", exports.empoex = "Empoex", exports.ccex = "C-Cex", +exports.crex = "Crex24", +exports.tradesatoshi = "TradeSatoshi", exports.reloadLocale = function reloadLocale(locale) { // Discover where the locale file lives diff --git a/lib/markets/altmarkets.js b/lib/markets/altmarkets.js new file mode 100644 index 000000000..41d1d36dc --- /dev/null +++ b/lib/markets/altmarkets.js @@ -0,0 +1,110 @@ +var request = require('request'); + +var base_url = 'https://altmarkets.io/api/v2/'; + +function get_summary(coin, exchange, cb) { + var req_url = base_url + 'tickers/' + coin.toLowerCase() + exchange.toLowerCase(); + var summary = {}; + request({uri: req_url, json: true}, function (error, response, body) { + if (error) { + return cb(error, null); + } else { + if (body.error) { + return cb(body.error, null); + } else { + summary['bid'] = parseFloat(body['ticker']['buy']).toFixed(8); + summary['ask'] = parseFloat(body['ticker']['sell']).toFixed(8); + summary['volume'] = parseFloat(body['ticker']['vol']).toFixed(8); + summary['volume_btc'] = parseFloat(body['ticker']['quotevol']).toFixed(8); + summary['high'] = parseFloat(body['ticker']['high']).toFixed(8); + summary['low'] = parseFloat(body['ticker']['low']).toFixed(8); + summary['last'] = parseFloat(body['ticker']['last']).toFixed(8); + summary['change'] = 0; + request({ uri: base_url + 'currency/trades?currency=' + coin.toLowerCase(), json: true }, function (error, response, body) { + if (error) { + return cb(null, summary); + } else { + if (body.error) { + return cb(null, summary); + } else { + summary['change'] = 0; + for (var i = 0; i < body.length; i++) { + if (exchange.toLowerCase() in body[i]) { + summary['change'] = parseFloat(body[i][exchange.toLowerCase()]['change']); + break; + } + } + return cb(null, summary); + } + } + }); + } + } + }); +} + +function get_trades(coin, exchange, cb) { + var req_url = base_url + "trades?market=" + coin.toLowerCase() + "" + exchange.toLowerCase() + "&limit=50&order_by=desc"; + request({uri: req_url, json: true}, function (error, response, body) { + if (body.error) { + return cb(body.error, null); + } else { + return cb (null, body); + } + }); +} + +function get_orders(coin, exchange, cb) { + var req_url = base_url + 'depth?market=' + coin.toLowerCase() + exchange.toLowerCase(); + request({uri: req_url, json: true}, function (error, response, body) { + if (body.error) { + return cb(body.error, [], []) + } else { + var orders = body; + var buys = []; + var sells = []; + if (orders['bids'].length > 0){ + for (var i = 0; i < orders['bids'].length; i++) { + var order = { + amount: parseFloat(orders.bids[i][1]).toFixed(8), + price: parseFloat(orders.bids[i][0]).toFixed(8), + // total: parseFloat(orders.bids[i].Total).toFixed(8) + // Necessary because API will return 0.00 for small volume transactions + total: (parseFloat(orders.bids[i][1]).toFixed(8) * parseFloat(orders.bids[i][0])).toFixed(8) + } + buys.push(order); + } + } else {} + if (orders['asks'].length > 0) { + for (var x = 0; x < orders['asks'].length; x++) { + var order = { + amount: parseFloat(orders.asks[x][1]).toFixed(8), + price: parseFloat(orders.asks[x][0]).toFixed(8), + // total: parseFloat(orders.asks[x].Total).toFixed(8) + // Necessary because API will return 0.00 for small volume transactions + total: (parseFloat(orders.asks[x][1]).toFixed(8) * parseFloat(orders.asks[x][0])).toFixed(8) + } + sells.push(order); + } + } else {} + var sells = sells.reverse(); + return cb(null, buys, sells); + } + }); +} + +module.exports = { + get_data: function(coin, exchange, cb) { + var error = null; + get_orders(coin, exchange, function(err, buys, sells) { + if (err) { error = err; } + get_trades(coin, exchange, function(err, trades) { + if (err) { error = err; } + get_summary(coin, exchange, function(err, stats) { + if (err) { error = err; } + return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats}); + }); + }); + }); + } +}; diff --git a/lib/markets/crex.js b/lib/markets/crex.js new file mode 100644 index 000000000..f885d3f41 --- /dev/null +++ b/lib/markets/crex.js @@ -0,0 +1,106 @@ +var request = require('request'); + +var base_url = 'https://api.crex24.com/v2/public'; + +function get_summary(coin, exchange, cb) { + var summary = {}; + var url=base_url + '/tickers?instrument=' + coin.toUpperCase() + '-' + exchange.toUpperCase(); + request({uri: url, json: true}, function (error, response, body) { + if (error) { + return cb(error, null); + } else if (body.error !== true) { + summary['ask'] = parseFloat(body[0]['ask']).toFixed(8); + summary['bid'] = parseFloat(body[0]['bid']).toFixed(8); + summary['volume'] = parseFloat(body[0]['baseVolume']).toFixed(8); + summary['volume_btc'] = parseFloat(body[0]['volumeInBtc']).toFixed(8); + summary['high'] = parseFloat(body[0]['high']).toFixed(8); + summary['low'] = parseFloat(body[0]['low']).toFixed(8); + summary['last'] = parseFloat(body[0]['last']).toFixed(8); + summary['change'] = parseFloat(body[0]['percentChange']); + return cb(null, summary); + } else { + return cb(error, null); + } + }); +} + +function get_trades(coin, exchange, cb) { + var req_url = base_url + '/recentTrades?instrument=' + coin.toUpperCase() + '-' + exchange.toUpperCase(); + request({ uri: req_url, json: true }, function (error, response, body) { + if(error) + return cb(error, null); + else if (body.error !== true) { + var tTrades = body; + var trades = []; + for (var i = 0; i < tTrades.length; i++) { + var Trade = { + orderpair: tTrades[i].Label, + ordertype: tTrades[i].side, + amount: parseFloat(tTrades[i].volume).toFixed(8), + price: parseFloat(tTrades[i].price).toFixed(8), + total: (parseFloat(tTrades[i].volume).toFixed(8) * parseFloat(tTrades[i].price)).toFixed(8), + timestamp: parseInt((new Date(tTrades[i].timestamp).getTime() / 1000).toFixed(0)) + } + trades.push(Trade); + } + return cb(null, trades); + } else { + return cb(body.Message, null); + } + }); +} + +function get_orders(coin, exchange, cb) { + var req_url = base_url + '/orderBook?instrument=' + coin.toUpperCase() + '-' + exchange.toUpperCase(); + request({ uri: req_url, json: true }, function (error, response, body) { + if(error) + return cb(error, null); + else if (body.error !== true) { + var buyorders = body['buyLevels']; + var sellorders = body['sellLevels']; + + var buys = []; + var sells = []; + if (buyorders.length > 0){ + for (var i = 0; i < buyorders.length; i++) { + var order = { + amount: parseFloat(buyorders[i].volume).toFixed(8), + price: parseFloat(buyorders[i].price).toFixed(8), + total: (parseFloat(buyorders[i].volume).toFixed(8) * parseFloat(buyorders[i].price)).toFixed(8) + } + buys.push(order); + } + } else {} + if (sellorders.length > 0) { + for (var x = 0; x < sellorders.length; x++) { + var order = { + amount: parseFloat(sellorders[x].volume).toFixed(8), + price: parseFloat(sellorders[x].price).toFixed(8), + total: (parseFloat(sellorders[x].volume).toFixed(8) * parseFloat(sellorders[x].price)).toFixed(8) + } + sells.push(order); + } + } else { + } + return cb(null, buys, sells); + } else { + return cb(body.Message, [], []) + } + }); +} + +module.exports = { + get_data: function(coin, exchange, cb) { + var error = null; + get_orders(coin, exchange, function(err, buys, sells) { + if (err) { error = err; } + get_trades(coin, exchange, function(err, trades) { + if (err) { error = err; } + get_summary(coin, exchange, function(err, stats) { + if (err) { error = err; } + return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats}); + }); + }); + }); + } +}; diff --git a/lib/markets/tradesatoshi.js b/lib/markets/tradesatoshi.js new file mode 100644 index 000000000..9de06a949 --- /dev/null +++ b/lib/markets/tradesatoshi.js @@ -0,0 +1,81 @@ +var request = require('request'); + +var base_url = 'https://tradesatoshi.com/api/public/'; +function get_summary(coin, exchange, cb) { + var summary = {}; + request({ uri: base_url + 'getmarketsummary?market=' + coin + '_' + exchange, json: true }, function (error, response, body) { + if (error) { + return cb(error, null); + } else { + summary['bid'] = body.result['bid']; + summary['ask'] = body.result['ask']; + summary['volume'] = body.result['volume']; + summary['high'] = body.result['high']; + summary['low'] = body.result['low']; + summary['last'] = body.result['last']; + summary['change'] = body.result['change']; + return cb(null, summary); + } + }); +} + +function get_trades(coin, exchange, cb) { + var req_url = base_url + 'getmarkethistory?market=' + coin + '_' + exchange + '&count=1000'; + request({uri: req_url, json: true}, function (error, response, body) { + if (body.success == true) { + return cb (null, body['result']); + } else { + return cb(body.message, null); + } + }); + } + +function get_orders(coin, exchange, cb) { + var req_url = base_url + 'getorderbook?market=' + coin + '_' + exchange + '&type=both&depth=1000'; + request({ uri: req_url, json: true }, function (error, response, body) { + if (body.success) { + var orders = body.result; + var buys = []; + var sells = []; + if (orders['buy'].length > 0){ + for (var i = 0; i < orders['buy'].length; i++) { + var order = { + amount: parseFloat(orders.buy[i].quantity).toFixed(8), + price: parseFloat(orders.buy[i].rate).toFixed(8), + total: (parseFloat(orders.buy[i].quantity).toFixed(8) * parseFloat(orders.buy[i].rate)).toFixed(8) + } + buys.push(order); + } + } + if (orders['sell'].length > 0) { + for (var x = 0; x < orders['sell'].length; x++) { + var order = { + amount: parseFloat(orders.sell[x].quantity).toFixed(8), + price: parseFloat(orders.sell[x].rate).toFixed(8), + total: (parseFloat(orders.sell[x].quantity).toFixed(8) * parseFloat(orders.sell[x].rate)).toFixed(8) + } + sells.push(order); + } + } + return cb(null, buys, sells); + } else { + return cb(body.Message, [], []) + } + }); +} + +module.exports = { + get_data: function(coin, exchange, cb) { + var error = null; + get_orders(coin, exchange, function(err, buys, sells) { + if (err) { error = err; } + get_trades(coin, exchange, function(err, trades) { + if (err) { error = err; } + get_summary(coin, exchange, function(err, stats) { + if (err) { error = err; } + return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats}); + }); + }); + }); + } + }; diff --git a/lib/settings.js b/lib/settings.js index 668e7c7c1..951de5983 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -105,7 +105,8 @@ exports.movement = { exports.index = { "show_hashrate": false, "difficulty": "POW", - "last_txs": 100 + "last_txs": 100, + "txs_per_page": 10 }; // twitter @@ -125,8 +126,11 @@ exports.check_timeout = 250; exports.genesis_tx = "65f705d2f385dc85763a317b3ec000063003d6b039546af5d8195a5ec27ae410"; exports.genesis_block = "b2926a56ca64e0cd2430347e383f63ad7092f406088b9b86d6d68c2a34baef51"; +exports.use_rpc = false; exports.heavy = false; +exports.lock_during_index = false; exports.txcount = 100; +exports.txcount_per_page = 50; exports.show_sent_received = true; exports.supply = "COINBASE"; exports.nethash = "getnetworkhashps"; diff --git a/locale/en.json b/locale/en.json index 443d75916..8387b7ea7 100644 --- a/locale/en.json +++ b/locale/en.json @@ -62,6 +62,7 @@ "a_menu_showing": "Showing last", "a_menu_txs": "transactions", "a_menu_all": "All", + "a_qr": "QR Code", //richlist "rl_received_coins": "Top 100 - Received Coins", @@ -75,6 +76,7 @@ "rl_top100": "Top 76-100", "rl_hundredplus": "101+", + "net_addnodes": "Add Nodes", "net_connections": "Connections", "net_address": "Address", "net_protocol": "Protocol", @@ -127,12 +129,15 @@ // Markets "poloniex": "Poloniex", "bittrex": "Bittrex", + "altmarkets": "AltMarkets", "bleutrade": "Bleutrade", "yobit": "Yobit", "empoex": "Empoex", "cryptsy": "Cryptsy", "cryptopia": "Cryptopia", "ccex": "C-Cex", + "crex": "Crex24", + "tradesatoshi": "TradeSatoshi", // Heavy rewards view "heavy_title": "Reward/voting information", diff --git a/models/address.js b/models/address.js index 348fc8b5b..d8780b79f 100644 --- a/models/address.js +++ b/models/address.js @@ -3,7 +3,6 @@ var mongoose = require('mongoose') var AddressSchema = new Schema({ a_id: { type: String, unique: true, index: true}, - txs: { type: Array, default: [] }, received: { type: Number, default: 0 }, sent: { type: Number, default: 0 }, balance: {type: Number, default: 0}, diff --git a/models/addresstx.js b/models/addresstx.js new file mode 100644 index 000000000..ceb50b8d0 --- /dev/null +++ b/models/addresstx.js @@ -0,0 +1,10 @@ +var mongoose = require('mongoose') + , Schema = mongoose.Schema; + +var AddressTXSchema = new Schema({ + a_id: { type: String, index: true}, + txid: { type: String, lowercase: true, index: true}, + balance: { type: Number, default: 0} +}, {id: false}); + +module.exports = mongoose.model('AddressTx', AddressTXSchema); diff --git a/models/peers.js b/models/peers.js index 887ffa222..1a739c9de 100644 --- a/models/peers.js +++ b/models/peers.js @@ -4,6 +4,7 @@ var mongoose = require('mongoose') var PeersSchema = new Schema({ createdAt: { type: Date, expires: 86400, default: Date.now()}, address: { type: String, default: "" }, + port: { type: String, default: "" }, protocol: { type: String, default: "" }, version: { type: String, default: "" }, country: { type: String, default: "" } diff --git a/package.json b/package.json index fcf52b44f..a03dd037f 100644 --- a/package.json +++ b/package.json @@ -8,20 +8,22 @@ "test": "node ./node_modules/jasmine/bin/jasmine.js" }, "dependencies": { - "express": "~4.2.0", - "static-favicon": "~1.0.0", - "morgan": "~1.0.0", - "cookie-parser": "~1.0.1", - "body-parser": "~1.0.0", - "debug": "~0.7.4", - "jade": "~1.3.0", + "bitcoin-core": "^2.0.0", "bitcoin-node-api": "0.1.0", - "request": "2.74.0", + "body-parser": "^1.19.0", + "cookie-parser": "^1.4.4", + "debug": "~4.1.1", + "express": "~4.17.1", + "intl": "^1.2.5", + "jade": "~1.3.0", "jsonminify": "0.2.3", - "mongodb": "2.0.45", - "mongoose": "4.1.10", "markdown-js": "0.0.3", - "qr-image": "~2.0.0" + "mongodb": "2.0.45", + "mongoose": "5.7.7", + "morgan": "~1.9.1", + "qr-image": "~2.0.0", + "request": "^2.88.0", + "static-favicon": "~1.0.0" }, "devDependencies": { "jasmine": "~2.1.0" diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 2c0b953c5..9bfb2eccc 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -152,10 +152,17 @@ tr { width: 150px !important; } } +.table>tbody>tr>td.addr-summary { + line-height: 7.7; + font-size: 1.3em; + font-weight: 800; + text-align: center; +} + .qrcode { - position: absolute; + /*position: absolute; top: 65px; - right: 15px; + right: 15px;*/ } .footer-logo { @@ -164,3 +171,8 @@ tr { bottom: 0px; } + +.decimal { + font-size: 0.7em; +} + diff --git a/routes/index.js b/routes/index.js index 73a5dc418..e1ebef70a 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,10 +1,10 @@ var express = require('express') - , router = express.Router() - , settings = require('../lib/settings') - , locale = require('../lib/locale') - , db = require('../lib/database') - , lib = require('../lib/explorer') - , qr = require('qr-image'); + , router = express.Router() + , settings = require('../lib/settings') + , locale = require('../lib/locale') + , db = require('../lib/database') + , lib = require('../lib/explorer') + , qr = require('qr-image'); function route_get_block(res, blockhash) { lib.get_block(blockhash, function (block) { @@ -89,32 +89,20 @@ function route_get_tx(res, txid) { } function route_get_index(res, error) { - res.render('index', { active: 'home', error: error, warning: null}); + db.is_locked(function(locked) { + if (locked) { + res.render('index', { active: 'home', error: error, warning: locale.initial_index_alert}); + } else { + res.render('index', { active: 'home', error: error, warning: null}); + } + }); } function route_get_address(res, hash, count) { db.get_address(hash, function(address) { if (address) { var txs = []; - var hashes = address.txs.reverse(); - if (address.txs.length < count) { - count = address.txs.length; - } - lib.syncLoop(count, function (loop) { - var i = loop.iteration(); - db.get_tx(hashes[i].addresses, function(tx) { - if (tx) { - txs.push(tx); - loop.next(); - } else { - loop.next(); - } - }); - }, function(){ - - res.render('address', { active: 'address', address: address, txs: txs}); - }); - + res.render('address', { active: 'address', address: address, txs: txs}); } else { route_get_index(res, hash + ' not found'); } @@ -206,7 +194,7 @@ router.get('/reward', function(req, res){ } else if (a.count > b.count) { return 1; } else { - return 0; + return 0; } }); @@ -216,19 +204,19 @@ router.get('/reward', function(req, res){ }); router.get('/tx/:txid', function(req, res) { - route_get_tx(res, req.param('txid')); + route_get_tx(res, req.params.txid); }); router.get('/block/:hash', function(req, res) { - route_get_block(res, req.param('hash')); + route_get_block(res, req.params.hash); }); router.get('/address/:hash', function(req, res) { - route_get_address(res, req.param('hash'), settings.txcount); + route_get_address(res, req.params.hash, settings.txcount); }); router.get('/address/:hash/:count', function(req, res) { - route_get_address(res, req.param('hash'), req.param('count')); + route_get_address(res, req.params.hash, req.params.count); }); router.post('/search', function(req, res) { @@ -269,8 +257,8 @@ router.post('/search', function(req, res) { }); router.get('/qr/:string', function(req, res) { - if (req.param('string')) { - var address = qr.image(req.param('string'), { + if (req.params.string) { + var address = qr.image(req.params.string, { type: 'png', size: 4, margin: 1, @@ -285,12 +273,12 @@ router.get('/ext/summary', function(req, res) { lib.get_difficulty(function(difficulty) { difficultyHybrid = '' if (difficulty['proof-of-work']) { - if (settings.index.difficulty == 'Hybrid') { - difficultyHybrid = 'POS: ' + difficulty['proof-of-stake']; - difficulty = 'POW: ' + difficulty['proof-of-work']; - } else if (settings.index.difficulty == 'POW') { - difficulty = difficulty['proof-of-work']; - } else { + if (settings.index.difficulty == 'Hybrid') { + difficultyHybrid = 'POS: ' + difficulty['proof-of-stake']; + difficulty = 'POW: ' + difficulty['proof-of-work']; + } else if (settings.index.difficulty == 'POW') { + difficulty = difficulty['proof-of-work']; + } else { difficulty = difficulty['proof-of-stake']; } } diff --git a/scripts/peers.js b/scripts/peers.js index 4c42f189c..17909f00e 100644 --- a/scripts/peers.js +++ b/scripts/peers.js @@ -27,14 +27,22 @@ mongoose.connect(dbString, function(err) { lib.syncLoop(body.length, function (loop) { var i = loop.iteration(); var address = body[i].addr.split(':')[0]; + var port = body[i].addr.split(':')[1]; db.find_peer(address, function(peer) { if (peer) { + if (isNaN(peer['port']) || peer['port'].length < 2 || peer['country'].length < 1) { + db.drop_peers(function() { + console.log('Saved peers missing ports or country, dropping peers. Re-reun this script afterwards.'); + exit(); + }); + } // peer already exists loop.next(); } else { - request({uri: 'http://freegeoip.net/json/' + address, json: true}, function (error, response, geo) { + request({uri: 'https://freegeoip.app/json/' + address, json: true}, function (error, response, geo) { db.create_peer({ address: address, + port: port, protocol: body[i].version, version: body[i].subver.replace('/', '').replace('/', ''), country: geo.country_name diff --git a/scripts/sync.js b/scripts/sync.js index f7728a8c2..d01ce50f8 100644 --- a/scripts/sync.js +++ b/scripts/sync.js @@ -1,9 +1,10 @@ var mongoose = require('mongoose') , db = require('../lib/database') - , Tx = require('../models/tx') - , Address = require('../models/address') - , Richlist = require('../models/richlist') - , Stats = require('../models/stats') + , Tx = require('../models/tx') + , Address = require('../models/address') + , AddressTx = require('../models/addresstx') + , Richlist = require('../models/richlist') + , Stats = require('../models/stats') , settings = require('../lib/settings') , fs = require('fs'); @@ -23,10 +24,10 @@ function usage() { console.log('check checks index for (and adds) any missing transactions/addresses'); console.log('reindex Clears index then resyncs from genesis to current block'); console.log(''); - console.log('notes:'); + console.log('notes:'); console.log('* \'current block\' is the latest created block when script is executed.'); console.log('* The market database only supports (& defaults to) reindex mode.'); - console.log('* If check mode finds missing data(ignoring new data since last sync),'); + console.log('* If check mode finds missing data(ignoring new data since last sync),'); console.log(' index_timeout in settings.json is set too low.') console.log(''); process.exit(0); @@ -39,17 +40,20 @@ if (process.argv[2] == 'index') { } else { switch(process.argv[3]) { - case 'update': - mode = 'update'; - break; - case 'check': - mode = 'check'; - break; - case 'reindex': - mode = 'reindex'; - break; - default: - usage(); + case 'update': + mode = 'update'; + break; + case 'check': + mode = 'check'; + break; + case 'reindex': + mode = 'reindex'; + break; + case 'reindex-rich': + mode = 'reindex-rich'; + break; + default: + usage(); } } } else if (process.argv[2] == 'market'){ @@ -87,7 +91,7 @@ function remove_lock(cb) { }); } else { return cb(); - } + } } function is_locked(cb) { @@ -102,7 +106,7 @@ function is_locked(cb) { }); } else { return cb(); - } + } } function exit() { @@ -140,34 +144,38 @@ is_locked(function (exists) { db.get_stats(settings.coin, function(stats){ if (settings.heavy == true) { db.update_heavy(settings.coin, stats.count, 20, function(){ - + }); } if (mode == 'reindex') { - Tx.remove({}, function(err) { - Address.remove({}, function(err2) { - Richlist.update({coin: settings.coin}, { - received: [], - balance: [], - }, function(err3) { - Stats.update({coin: settings.coin}, { - last: 0, - }, function() { - console.log('index cleared (reindex)'); - }); - db.update_tx_db(settings.coin, 1, stats.count, settings.update_timeout, function(){ - db.update_richlist('received', function(){ - db.update_richlist('balance', function(){ - db.get_stats(settings.coin, function(nstats){ - console.log('reindex complete (block: %s)', nstats.last); - exit(); + Tx.deleteMany({}, function(err) { + Address.deleteMany({}, function(err2) { + AddressTx.deleteMany({}, function(err3) { + Richlist.updateOne({coin: settings.coin}, { + received: [], + balance: [], + }, function(err3) { + Stats.updateOne({coin: settings.coin}, { + last: 0, + count: 0, + supply: 0, + }, function() { + console.log('index cleared (reindex)'); + }); + db.update_tx_db(settings.coin, 1, stats.count, settings.update_timeout, function(){ + db.update_richlist('received', function(){ + db.update_richlist('balance', function(){ + db.get_stats(settings.coin, function(nstats){ + console.log('reindex complete (block: %s)', nstats.last); + exit(); + }); }); }); }); }); }); }); - }); + }); } else if (mode == 'check') { db.update_tx_db(settings.coin, 1, stats.count, settings.check_timeout, function(){ db.get_stats(settings.coin, function(nstats){ @@ -186,6 +194,34 @@ is_locked(function (exists) { }); }); }); + } else if (mode == 'reindex-rich') { + console.log('update started'); + db.update_tx_db(settings.coin, stats.last, stats.count, settings.check_timeout, function(){ + console.log('update finished'); + db.check_richlist(settings.coin, function(exists){ + if (exists == true) { + console.log('richlist entry found, deleting now..'); + } + db.delete_richlist(settings.coin, function(deleted) { + if (deleted == true) { + console.log('richlist entry deleted'); + } + db.create_richlist(settings.coin, function() { + console.log('richlist created.'); + db.update_richlist('received', function(){ + console.log('richlist updated received.'); + db.update_richlist('balance', function(){ + console.log('richlist updated balance.'); + db.get_stats(settings.coin, function(nstats){ + console.log('update complete (block: %s)', nstats.last); + exit(); + }); + }); + }); + }); + }); + }); + }); } }); }); diff --git a/settings.json.template b/settings.json.template index 0f898d68b..03d1d7c24 100644 --- a/settings.json.template +++ b/settings.json.template @@ -45,6 +45,8 @@ "check_timeout": 250, // wallet settings + "use_rpc": false, + "wallet": { "host": "localhost", "port": 9332, @@ -76,7 +78,8 @@ "index": { "show_hashrate": true, "difficulty": "POW", - "last_txs": 100 + "last_txs": 100, + "txs_per_page": 10 }, // ensure links on API page are valid @@ -128,8 +131,12 @@ //heavy (enable/disable additional heavy features) "heavy": false, + //disable saving blocks & TXs via API during indexing. + "lock_during_index": false, + //amount of txs to index per address (stores latest n txs) "txcount": 100, + "txcount_per_page": 50, //show total sent & received on address page (set false if PoS) "show_sent_received": true, diff --git a/views/address.jade b/views/address.jade index 132520c6e..4797fd568 100644 --- a/views/address.jade +++ b/views/address.jade @@ -1,53 +1,55 @@ extends layout block content - - var balance = (address.received - address.sent) / 100000000; - - var sent = address.sent /100000000 - - var received = address.received / 100000000 - img.qrcode.pull-right.hidden-xs(src='/qr/#{address.a_id}') + - var balance = ((address.received - address.sent) / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + - var balanceParts = balance.split('.'); + - var sent = (address.sent /100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + - var sentParts = sent.split('.'); + - var received = (address.received / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + - var receivedParts = received.split('.'); .col-xs-12.col-md-10.col-md-offset-1 .panel.panel-default.panel-address-summary - .panel-heading(style='position:relative;') + .panel-heading(style='position:relative;') strong #{address.a_id} if settings.labels[address.a_id] - if settings.labels[address.a_id].type + if settings.labels[address.a_id].type label(class='label label-#{settings.labels[address.a_id].type} pull-right hidden-xs', style='margin-left:15px;') =settings.labels[address.a_id].label if settings.labels[address.a_id].url a(href='#{settings.labels[address.a_id].url}', target='_blank') span.fa.fa-question-circle(style='margin-left:5px;') else - label.label.label-default.pull-right.hidden-xs(style='margin-left:15px;') + label.label.label-default.pull-right.hidden-xs(style='margin-left:15px;') =settings.labels[address.a_id].label if settings.labels[address.a_id].url a(href='#{settings.labels[address.a_id].url}', target='_blank') span.fa.fa-question-circle(style='margin-left:5px;') - table.table.table-bordered.table-striped.summary-table + table.table.table-bordered.table-striped.summary-table thead tr if settings.show_sent_received == true - th #{settings.locale.total_sent} (#{settings.symbol}) + th.hidden-xs #{settings.locale.total_sent} (#{settings.symbol}) if address.a_id !== 'coinbase' if settings.show_sent_received == true - th #{settings.locale.total_received} (#{settings.symbol}) + th.hidden-xs #{settings.locale.total_received} (#{settings.symbol}) th #{settings.locale.rl_balance} (#{settings.symbol}) - tbody + th #{settings.locale.a_qr} + tbody tr - if settings.show_sent_received == true - td #{sent.toFixed(8)} + if settings.show_sent_received == true + td.addr-summary.hidden-xs #{sentParts[0]}. + span.decimal #{sentParts[1]} if address.a_id !== 'coinbase' if settings.show_sent_received == true - td #{received.toFixed(8)} - td #{balance.toFixed(8)} - .panel.panel-default.hidden-xs - .panel-heading - strong #{settings.locale.ex_latest_transactions} - table.table.table-bordered.table-striped.history-table - include ./includes/address_history.jade - .panel.panel-default.hidden-lg.hidden-md + td.addr-summary.hidden-xs #{receivedParts[0]}. + span.decimal #{receivedParts[1]} + td.addr-summary #{balanceParts[0]}. + span.decimal #{balanceParts[1]} + td.addr-summary + img.qrcode(src='/qr/#{address.a_id}') + .panel.panel-default .panel-heading strong #{settings.locale.ex_latest_transactions} table.table.table-bordered.table-striped - include ./includes/address_history.jade + include ./includes/address_history.jade .footer-padding - \ No newline at end of file diff --git a/views/block.jade b/views/block.jade index 2ce7ba956..0601a7a4f 100644 --- a/views/block.jade +++ b/views/block.jade @@ -91,10 +91,15 @@ block content a(href='/tx/#{txn.txid}') #{txn.txid} td #{txn.vout.length} if txn.vout.length > 0 - - var total = txn.total / 100000000 - td #{total.toFixed(8)} + - var total = (txn.total / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + - var totalParts = total.split('.') + td #{totalParts[0]}. + span.decimal #{totalParts[1]} else - td #{txn.total.toFixed(8)} + - var total = (txn.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + - var totalParts = total.split('.') + td #{totalParts[0]}. + span.decimal #{totalParts[1]} td.view_tx a(href='/tx/#{txn.txid}') span.glyphicon.glyphicon-eye-open diff --git a/views/includes/address_history.jade b/views/includes/address_history.jade index b0d5b90f8..218e0fa8e 100644 --- a/views/includes/address_history.jade +++ b/views/includes/address_history.jade @@ -1,39 +1,89 @@ -thead - tr - th.hidden-xs #{settings.locale.timestamp} - th.hidden-xs #{settings.locale.tx_hash} - th #{settings.locale.mkt_amount} (#{settings.symbol}) -tbody - each tx in txs - - var time = format_unixtime(tx.timestamp) +script. + var hashAddress = "#{address.a_id}"; + var setting_maxTxCount = parseInt("#{settings.txcount}"); + var setting_txPerPage = parseInt("#{settings.txcount_per_page}"); + var lengthMenuOpts = []; + var lengthMenuOptsAdd = [ 10, 25, 50, 75, 100, 250, 500, 1000 ]; + for (i=0; i < lengthMenuOptsAdd.length; i++) { + if (setting_maxTxCount >= lengthMenuOptsAdd[i]) { + lengthMenuOpts.push(lengthMenuOptsAdd[i]); + } + } + if (setting_maxTxCount < setting_txPerPage) { + var displayLengthMax = setting_maxTxCount; + } else { + var displayLengthMax = setting_txPerPage; + } + + $(document).ready(function () { + var rtable = $('#address-txs').dataTable({ + autoWidth: true, + searching: false, + ordering: false, + responsive: true, + lengthChange: true, + processing: true, + serverSide: true, + iDisplayLength: displayLengthMax, + lengthMenu: lengthMenuOpts, + ajax: { + "url": '/ext/getaddresstxsajax/'+hashAddress + }, + rowCallback: function (row, data, index) { + var timestamp = data[0]; //variables for better readability + var txhash = data[1]; //variables for better readability + var out = data[2]; //variables for better readability + var vin = data[3]; //variables for better readability + var balance = (data[4] / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); //variables for better readability + + var balanceParts = balance.split('.'); + + $("td:eq(0)", row).html(timestamp).addClass('hidden-xs'); + $("td:eq(1)", row).html('' + txhash + '').addClass('hidden-xs'); + + var amount = 0; + var updown = ""; + var rowclass = "info"; + + if(out > 0 && vin > 0) { + amount = (out - vin) / 100000000 + if (amount < 0) { + amount = (amount * -1).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + updown = "-"; + //- $("td:eq(2)", row).html("-" + amount).addClass("info"); + } else if (amount > 0) { + amount = amount.toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + updown = "+"; + //- $("td:eq(2)", row).html("+" + amount).addClass("info"); + } else { + amount = amount.toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + //- $("td:eq(2)", row).html(amount).addClass("info"); + } + } else if (out > 0) { + amount = (out / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + updown = "+"; + rowclass = "success"; + //- $("td:eq(2)", row).html("+" + amount).addClass("success"); + } else { + amount = (vin / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); + updown = "-"; + rowclass = "danger"; + //- $("td:eq(2)", row).html("-" + amount).addClass("danger"); + } + var amountParts = amount.split('.'); + $("td:eq(2)", row).html(updown + amountParts[0] + '.' + amountParts[1] + '').addClass(rowclass); + $("td:eq(3)", row).html(balanceParts[0] + '.' + balanceParts[1] + ''); + + }, + }); + }); +table#address-txs.table.table-bordered.table-striped + thead tr - td.hidden-xs #{time} - td.hidden-xs - a(href='/tx/#{tx.txid}') #{tx.txid} - - var done = false - - var out = 0 - - var vin = 0 - each r in tx.vout - if r.addresses == address.a_id - - out = r.amount - each s in tx.vin - if s.addresses == address.a_id - - vin = s.amount - if out > 0 && vin > 0 - td.info - - var amount = (out - vin) / 100000000 - if amount < 0 - - amount = amount * -1 - | - #{amount.toFixed(8)} - else if amount > 0 - | + #{amount.toFixed(8)} - else - | #{amount.toFixed(8)} - else if out > 0 - td.success - - var amount = out / 100000000 - | + #{amount.toFixed(8)} - else - td.danger - - var amount = vin / 100000000 - | - #{amount.toFixed(8)} \ No newline at end of file + th.hidden-xs #{settings.locale.timestamp} + th.hidden-xs #{settings.locale.tx_hash} + th #{settings.locale.mkt_amount} + span.small (#{settings.symbol}) + th #{settings.locale.rl_balance} + span.small (#{settings.symbol}) + tbody diff --git a/views/includes/net_addnodes.jade b/views/includes/net_addnodes.jade new file mode 100644 index 000000000..9275ef474 --- /dev/null +++ b/views/includes/net_addnodes.jade @@ -0,0 +1,71 @@ +script. + $(document).ready(function(){ + var ctable = $('#addnodes-table').dataTable( { + autoWidth: true, + searching: false, + ordering: false, + responsive: true, + lengthChange: true, + processing: true, + ajax: { + url: '/ext/connections', + dataSrc: function ( json ) { + var rows = [] + for ( var i=0;i Open Wallet Configuration File* + table#addnodes-table.table.table-bordered.table-striped + thead + tr + th.text-center AddNode Config Lines + tbody.text-center + + .panel-body + :markdown + Alternatively you can try one of these lines in the coin wallet debug window, or add them with *coindaemon*-cli + table#addnodes2-table.table.table-bordered.table-striped + thead + tr + th.text-center OneTry Node Lines + tbody.text-center diff --git a/views/includes/net_list.jade b/views/includes/net_list.jade new file mode 100644 index 000000000..ca9034342 --- /dev/null +++ b/views/includes/net_list.jade @@ -0,0 +1,40 @@ +script. + $(document).ready(function(){ + var ctable = $('#connections-table').dataTable( { + autoWidth: true, + searching: false, + ordering: false, + responsive: true, + lengthChange: true, + processing: true, + ajax: { + url: '/ext/connections', + dataSrc: function ( json ) { + /*for ( var i=0;i" + json.data[i]['txid'] + ""; + json.data[i]['blockindex'] = "" + json.data[i]['blockindex'] + ""; + var amount = json.data[i]['total'] / 100000000; + json.data[i]['total'] = amount.toFixed(8); + json.data[i]['recipients'] = json.data[i]['vout'].length; + }*/ + return json.data; + } + }, + columns: [ + { data: 'address', width: '25%' }, + { data: 'protocol', width: '25%' }, + { data: 'version', width:'25%' }, + { data: 'country', width: '25%'} + ] + }); + }); +.panel.panel-default + table#connections-table.table.table-bordered.table-striped + thead + tr + th.text-center #{settings.locale.net_address} + th.text-center #{settings.locale.net_protocol} + th.text-center #{settings.locale.net_subversion} + th.text-center #{settings.locale.net_country} + tbody.text-center diff --git a/views/includes/rl_balance.jade b/views/includes/rl_balance.jade index 0b8c29fdc..33248d51f 100644 --- a/views/includes/rl_balance.jade +++ b/views/includes/rl_balance.jade @@ -13,15 +13,16 @@ - var count = 0 each item in balance - count = count + 1 - - var itemFixed = item.balance / 100000000 - - var percentage = (itemFixed / stats.supply) * 100 + - var itemFixed = (parseInt(item.balance) / 100000000) + - var itemFixedParts = itemFixed.toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}).split("."); + - var percentage = (itemFixed / stats.supply) * 100; tr td(style='text-align:center;') =count td a(href='/address/#{item.a_id}') #{item.a_id} include ./rl_labels.jade - td.hidden-xs - =itemFixed.toFixed(8) + td.hidden-xs #{itemFixedParts[0]}. + span.decimal #{itemFixedParts[1]} td.hidden-xs(style='text-align:center;') - =percentage.toFixed(2) \ No newline at end of file + =percentage.toFixed(2) diff --git a/views/includes/rl_received.jade b/views/includes/rl_received.jade index b8b934ea2..3f04860dd 100644 --- a/views/includes/rl_received.jade +++ b/views/includes/rl_received.jade @@ -11,13 +11,14 @@ tbody - var count = 0 each item in received - - count = count + 1 - - var itemFixed = item.received / 100000000 + - count = count + 1; + - var itemFixed = (parseInt(item.received) / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + - var itemFixedParts = itemFixed.split("."); tr td(style='text-align:center;') =count td a(href='/address/#{item.a_id}') #{item.a_id} include ./rl_labels.jade - td.hidden-xs - =itemFixed.toFixed(8) \ No newline at end of file + td.hidden-xs #{itemFixedParts[0]}. + span.decimal #{itemFixedParts[1]} diff --git a/views/index.jade b/views/index.jade index 069226f5d..7b99a8e63 100644 --- a/views/index.jade +++ b/views/index.jade @@ -2,6 +2,20 @@ extends layout block content script. + var setting_maxTxCount = parseInt("#{settings.index.last_txs}"); + var setting_txPerPage = parseInt("#{settings.index.txs_per_page}"); + var lengthMenuOpts = []; + var lengthMenuOptsAdd = [ 10, 25, 50, 75, 100, 250, 500, 1000 ]; + for (i=0; i < lengthMenuOptsAdd.length; i++) { + if (setting_maxTxCount >= lengthMenuOptsAdd[i]) { + lengthMenuOpts.push(lengthMenuOptsAdd[i]); + } + } + if (setting_maxTxCount < setting_txPerPage) { + var displayLengthMax = setting_maxTxCount; + } else { + var displayLengthMax = setting_txPerPage; + } $(document).ready(function(){ var stable = $('#block-table').dataTable( { autoWidth: true, @@ -30,33 +44,31 @@ block content ] }); var rtable = $('#recent-table').dataTable( { - autoWidth: true, + autoWidth: false, searching: false, ordering: false, - responsive: true, + responsive: false, lengthChange: true, processing: true, - ajax: { - url: '/ext/getlasttxs/0.00000001', - dataSrc: function ( json ) { - for ( var i=0;i" + json.data[i]['txid'] + ""; - json.data[i]['blockindex'] = "" + json.data[i]['blockindex'] + ""; - var amount = json.data[i]['total'] / 100000000; - json.data[i]['total'] = amount.toFixed(8); - json.data[i]['recipients'] = json.data[i]['vout'].length; - } - return json.data; - } + serverSide: true, + iDisplayLength: displayLengthMax, + lengthMenu: lengthMenuOpts, + ajax: '/ext/getlasttxsajax/1', + rowCallback: function(row, data, index) { + var blockindex = data[0]; //variables for better readability + var blockhash = data[1]; //variables for better readability + var txhash = data[2]; //variables for better readability + var outputs = data[3]; //variables for better readability + var amount = (data[4] / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}); //variables for better readability + var amountParts = amount.split('.'); + var amount = amountParts[0] + '.' + amountParts[1] + ''; + var timestamp = data[5]; //variables for better readability + $("td:eq(0)", row).html('' + blockindex + ''); + $("td:eq(1)", row).html('' + txhash + '').addClass("d-none d-md-none d-lg-table-cell text-center hidden-xs"); + $("td:eq(2)", row).html(outputs).addClass("d-none d-md-none d-lg-table-cell text-center hidden-xs"); + $("td:eq(3)", row).html(amount); + $("td:eq(4)", row).html(timestamp); }, - columns: [ - { data: 'blockindex', width: '8%' }, - { data: 'txid', width: '40%' }, - { data: 'recipients', width:'5%' }, - { data: 'total', width: '15%' }, - { data: 'timestamp', width: '25%' }, - ] }); setInterval( function () { rtable.api().ajax.reload(null, false); diff --git a/views/info.jade b/views/info.jade index 7bcc9f918..07f893c04 100644 --- a/views/info.jade +++ b/views/info.jade @@ -100,14 +100,18 @@ block content *Returns information for given address* [#{address}/ext/getaddress/#{hashes.address}](/ext/getaddress/#{hashes.address}) + * **gettx (/ext/gettx/hash)** + *Returns information for given tx hash* + [#{address}/ext/gettx/#{hashes.txhash}](/ext/gettx/#{hashes.txhash}) + * **getbalance (/ext/getbalance/hash)** *Returns current balance of given address* [#{address}/ext/getbalance/#{hashes.address}](/ext/getbalance/#{hashes.address}) - * **getlasttxs (/ext/getlasttxs/count/min)** - *Returns last [count] transactions greater than [min]* + * **getlasttxsajax (/ext/getlasttxsajax/min)** + *Returns last transactions greater than [min]* *Note: returned values are in satoshis* - [#{address}/ext/getlasttxs/10/100](/ext/getlasttxs/10/100) + [#{address}/ext/getlasttxsajax/100](/ext/getlasttxsajax/100) ------ diff --git a/views/layout.jade b/views/layout.jade index 630e447da..430684357 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -17,19 +17,19 @@ html script(src='/vendor/jqplot/plugins/jqplot.barRenderer.min.js') script(src='/vendor/jqplot/plugins/jqplot.categoryAxisRenderer.min.js') script(src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment.min.js') - script(src='//cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js') + script(src='//cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js') script(src='//cdn.datatables.net/responsive/1.0.1/js/dataTables.responsive.js') script(src='//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.js') script(src='/javascripts/chart.js') script. $(document).ready(function(){ $('##{active}').addClass('active'); - function update_stats(){ + function update_stats(){ $.ajax({url: '/ext/summary', success: function(json){ - $("#supply").text(json.data[0].supply); - $("#difficulty").text(json.data[0].difficulty); + $("#supply").text(parseInt(parseFloat(json.data[0].supply).toFixed(0)).toLocaleString('en')); + $("#difficulty").text(json.data[0].difficulty.toFixed(2)); $("#difficultyHybrid").text(json.data[0].difficultyHybrid); - $("#hashrate").text(json.data[0].hashrate); + $("#hashrate").text(parseFloat(json.data[0].hashrate).toLocaleString('en')); $("#lastPrice").text(json.data[0].lastPrice.toFixed(8)); $("#lblConnections").text(json.data[0].connections + ' connections'); $("#lblBlockcount").text(json.data[0].blockcount + ' blocks'); @@ -38,15 +38,15 @@ html } $('.block-last-next').tooltip({ animation: true, - delay: { hide: 500 }, + delay: { hide: 500 }, }); - $('.history-table').dataTable( { + $('.history-table').dataTable( { searching: false, ordering: false, responsive: true }); $('.iquidus').css('color',$('.table').css('color')); - $(window).resize(function () { + $(window).resize(function () { $($.fn.dataTable.tables( true ) ).DataTable().columns.adjust(); }); setInterval( function() { @@ -56,7 +56,7 @@ html }); body - function format_unixtime(unixtime) { - - var a = new Date(unixtime*1000); + - var a = new Date(unixtime*1000); - var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; - var year = a.getFullYear(); - var month = months[a.getMonth()]; @@ -77,7 +77,7 @@ html - min = '0' + min if sec < 10 - sec = '0' + sec - - var time = date + suffix + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ; + - var time = date + suffix + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ; - return time - }; .nav.navbar.navbar-default.navbar-fixed-top(role='navigation') @@ -92,7 +92,7 @@ html .collapse.navbar-collapse(id='navbar-collapse') ul.nav.navbar-nav li#home - a.navbar-link(href='/') + a.navbar-link(href='/') span.glyphicon.glyphicon-search span.menu-text #{settings.locale.menu_explorer} if settings.heavy == true @@ -101,8 +101,8 @@ html span.fa.fa-star span.menu-text #{settings.locale.menu_reward} if settings.display.movement == true - li#movement - a.navbar-link.loading(href='/movement') + li#movement + a.navbar-link.loading(href='/movement') span.fa.fa-money span.menu-text #{settings.locale.menu_movement} if settings.display.network == true @@ -117,7 +117,7 @@ html span.menu-text #{settings.locale.menu_richlist} if settings.display.markets == true li#markets - a.navbar-link.loading(href='/markets/#{settings.markets.default}') + a.navbar-link.loading(href='/markets/#{settings.markets.default}') span.fa.fa-line-chart span.menu-text #{settings.locale.menu_markets} if settings.display.api == true @@ -144,11 +144,11 @@ html strong #{settings.locale.network} (H/s) .panel-body if settings.index.difficulty == 'Hybrid' - label#hashrate - + label#hashrate -
- label#lblX1 - else - label#hashrate - + label#lblX1 + else + label#hashrate - .col-md-2 .panel.panel-default.hidden-sm.hidden-xs .panel-heading @@ -168,11 +168,11 @@ html strong #{settings.locale.ex_supply} (#{settings.symbol}) .panel-body if settings.index.difficulty == 'Hybrid' - label#supply - + label#supply -
label#lblX1 else - label#supply - + label#supply - .col-md-2 .panel.panel-default.hidden-sm.hidden-xs .panel-heading @@ -183,10 +183,10 @@ html
label#lblX1 else - label#lastPrice - - + label#lastPrice - + .row.text-center(style='margin-top:10px;margin-bottom:20px;') - form.form-inline(method='post', action='/search') + form.form-inline(method='post', action='/search') #index-search.form-group input.form-control(type='text', name='search', placeholder='#{settings.locale.ex_search_message}', style='min-width:80%;margin-right:5px;') button.btn.btn-success(type='submit') #{settings.locale.ex_search_button} @@ -213,14 +213,11 @@ html li.pull-left a#youtube-icon(href="https://www.youtube.com/channel/#{settings.youtube}", target='_blank') span.glyphicon.fa.fa-youtube - .col-md-4 + .col-md-4 ul.nav li.text-center(style='margin-left:80px;margin-right:80px;') - p(style='margin-top:15px;') - a.navbar-link(href='https://github.com/iquidus/explorer', target='_blank') Powered by Iquidus Explorer + p(style='margin-top:15px;') + a.navbar-link(href='https://github.com/iquidus/explorer', target='_blank') Powered by Iquidus Explorer span.connections label#lblBlockcount.label.label-default - label#lblConnections.label.label-default - - - - diff --git a/views/markets/altmarkets.jade b/views/markets/altmarkets.jade new file mode 100644 index 000000000..3e611b84b --- /dev/null +++ b/views/markets/altmarkets.jade @@ -0,0 +1,117 @@ +extends menu + +block market_view + .row + .col-md-12 + .panel.panel-default + .panel-heading + strong #{settings.locale.altmarkets} - #{marketdata.coin}/#{marketdata.exchange} - #{settings.locale.mkt_hours} + a(href='#') + span.fa.fa-line-chart.pull-right.view-chart-disabled.iquidus.market-toggle(data-toggle='tooltip', data-placement='bottom', title='#{settings.locale.mkt_no_chart}') + table.table.table-bordered.summary-table + thead + tr + th #{settings.locale.mkt_high} + th #{settings.locale.mkt_low} + th #{settings.locale.mkt_volume} (#{marketdata.coin}) + th #{settings.locale.mkt_volume} (#{marketdata.exchange}) + th.hidden-xs #{settings.locale.mkt_top_bid} + th.hidden-xs #{settings.locale.mkt_top_ask} + th.hidden-xs #{settings.locale.mkt_last} + th #{settings.locale.mkt_change} + tbody + tr + td #{marketdata.data.summary.high} + td #{marketdata.data.summary.low} + td #{marketdata.data.summary.volume} + td #{marketdata.data.summary.volume_btc} + td.hidden-xs #{marketdata.data.summary.bid} + td.hidden-xs #{marketdata.data.summary.ask} + td.hidden-xs #{marketdata.data.summary.last} + - var altmarkets_change = (marketdata.data.summary.change); + if altmarkets_change >= 0 + td.success +#{altmarkets_change} % + else + td.danger #{altmarkets_change} % + .row + .col-md-6.col-xs-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_buy_orders} + table.table.table-striped.table-bordered.order-table + thead + tr + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th.hidden-xs #{settings.locale.mkt_total} (#{marketdata.exchange}) + tbody + each buy in marketdata.data.buys + tr + td + =buy.price + td + =buy.amount + td.hidden-xs + =buy.total + + .col-md-6.col-xs-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_sell_orders} + table.table.table-striped.table-bordered.order-table + thead + tr + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th.hidden-xs #{settings.locale.mkt_total} (#{marketdata.exchange}) + tbody + each sell in marketdata.data.sells + tr + td + =sell.price + td + =sell.amount + td.hidden-xs + =sell.total + .row + .col-md-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_trade_history} + table.table.table-hover.history-table.table-bordered(cellspacing="0") + thead + tr + th.hidden-xs #{settings.locale.mkt_type} + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_total} (#{marketdata.exchange}) + th.hidden-xs #{settings.locale.mkt_time_stamp} + tbody + each order in marketdata.data.history + if order.side == 'up' + tr.success + td.hidden-xs + =order.side + td + =parseFloat(order.volume).toFixed(8) + td + =parseFloat(order.price).toFixed(8) + td + =parseFloat(order.funds).toFixed(8) + td.hidden-xs + =(new Date(order.timestamp * 1000).toUTCString()) + else + tr.danger + td.hidden-xs + =order.side + td + =parseFloat(order.volume).toFixed(8) + td + =parseFloat(order.price).toFixed(8) + td + =parseFloat(order.funds).toFixed(8) + td.hidden-xs + =(new Date(order.timestamp * 1000).toUTCString()) + + .footer-padding + diff --git a/views/markets/crex.jade b/views/markets/crex.jade new file mode 100644 index 000000000..1dc2a7036 --- /dev/null +++ b/views/markets/crex.jade @@ -0,0 +1,113 @@ +extends menu + +block market_view + .row + .col-md-12 + .panel.panel-default + .panel-heading + strong #{settings.locale.crex} - #{marketdata.coin}/#{marketdata.exchange} - #{settings.locale.mkt_hours} + a(href='#') + span.fa.fa-line-chart.pull-right.view-chart-disabled.iquidus.market-toggle(data-toggle='tooltip', data-placement='bottom', title='#{settings.locale.mkt_no_chart}') + table.table.table-bordered.summary-table + thead + tr + th #{settings.locale.mkt_high} + th #{settings.locale.mkt_low} + th #{settings.locale.mkt_volume} + th.hidden-xs #{settings.locale.mkt_top_bid} + th.hidden-xs #{settings.locale.mkt_top_ask} + th.hidden-xs #{settings.locale.mkt_last} + th #{settings.locale.mkt_change} + tbody + tr + td #{marketdata.data.summary.high} + td #{marketdata.data.summary.low} + td #{marketdata.data.summary.volume} + td.hidden-xs #{marketdata.data.summary.bid} + td.hidden-xs #{marketdata.data.summary.ask} + td.hidden-xs #{marketdata.data.summary.last} + - var crex_change = (marketdata.data.summary.change); + if crex_change >= 0 + td.success +#{crex_change} % + else + td.danger #{crex_change} % + .row + .col-md-6.col-xs-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_buy_orders} + table.table.table-hover.history-table.table-bordered(cellspacing="0") + thead + tr + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th.hidden-xs #{settings.locale.mkt_total} (#{marketdata.exchange}) + tbody + each buy in marketdata.data.buys + tr + td + =buy.price + td + =buy.amount + td.hidden-xs + =buy.total + .col-md-6.col-xs-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_sell_orders} + table.table.table-hover.history-table.table-bordered(cellspacing="0") + thead + tr + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th.hidden-xs #{settings.locale.mkt_total} (#{marketdata.exchange}) + tbody + each sell in marketdata.data.sells + tr + td + =sell.price + td + =sell.amount + td.hidden-xs + =sell.total + .row + .col-md-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_trade_history} + table.table.table-hover.history-table.table-bordered(cellspacing="0") + thead + tr + th.hidden-xs #{settings.locale.mkt_type} + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_total} (#{marketdata.exchange}) + th.hidden-xs #{settings.locale.mkt_time_stamp} + tbody + each order in marketdata.data.history + if order.ordertype == "sell" + tr.danger + td.hidden-xs + =order.ordertype + td + =order.amount + td + =order.price + td + =order.total + td.hidden-xs + =format_unixtime(order.timestamp) + else + tr.success + td.hidden-xs + =order.ordertype + td + =order.amount + td + =order.price + td + =order.total + td.hidden-xs + =format_unixtime(order.timestamp) + + .footer-padding diff --git a/views/markets/tradesatoshi.jade b/views/markets/tradesatoshi.jade new file mode 100644 index 000000000..0093af628 --- /dev/null +++ b/views/markets/tradesatoshi.jade @@ -0,0 +1,113 @@ +extends menu + +block market_view + .row + .col-md-12 + .panel.panel-default + .panel-heading + strong #{settings.locale.tradesatoshi} - #{marketdata.coin}/#{marketdata.exchange} - #{settings.locale.mkt_hours} + a(href='#') + span.fa.fa-line-chart.pull-right.view-chart-disabled.iquidus.market-toggle(data-toggle='tooltip', data-placement='bottom', title='#{settings.locale.mkt_no_chart}') + table.table.table-bordered.summary-table + thead + tr + th #{settings.locale.mkt_high} + th #{settings.locale.mkt_low} + th #{settings.locale.mkt_volume} + th.hidden-xs #{settings.locale.mkt_top_bid} + th.hidden-xs #{settings.locale.mkt_top_ask} + th.hidden-xs #{settings.locale.mkt_last} + th #{settings.locale.mkt_change} + tbody + tr + td #{marketdata.data.summary.high.toFixed(8)} + td #{marketdata.data.summary.low.toFixed(8)} + td #{marketdata.data.summary.volume.toFixed(2).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1")} + td.hidden-xs #{marketdata.data.summary.bid.toFixed(8)} + td.hidden-xs #{marketdata.data.summary.ask.toFixed(8)} + td.hidden-xs #{marketdata.data.summary.last.toFixed(8)} + - var tradesatoshi_change = parseFloat(marketdata.data.summary.change).toFixed(4).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1"); + if tradesatoshi_change >= 0 + td.success +#{tradesatoshi_change} % + else + td.danger #{tradesatoshi_change} % + .row + .col-md-6.col-xs-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_buy_orders} + table.table.table-hover.history-table.table-bordered(cellspacing="0") + thead + tr + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th.hidden-xs #{settings.locale.mkt_total} (#{marketdata.exchange}) + tbody + each buy in marketdata.data.buys + tr + td + =buy.price + td + =buy.amount + td.hidden-xs + =buy.total + + .col-md-6.col-xs-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_sell_orders} + table.table.table-hover.history-table.table-bordered(cellspacing="0") + thead + tr + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th.hidden-xs #{settings.locale.mkt_total} (#{marketdata.exchange}) + tbody + each sell in marketdata.data.sells + tr + td + =sell.price + td + =sell.amount + td.hidden-xs + =sell.total + .row + .col-md-12 + .panel.panel-default + .panel-heading + h3.panel-title #{settings.locale.mkt_trade_history} + table.table.table-hover.history-table.table-bordered(cellspacing="0") + thead + tr + th.hidden-xs #{settings.locale.mkt_type} + th #{settings.locale.mkt_amount} (#{marketdata.coin}) + th #{settings.locale.mkt_price} (#{marketdata.exchange}) + th #{settings.locale.mkt_total} (#{marketdata.exchange}) + th.hidden-xs #{settings.locale.mkt_time_stamp} + tbody + each result in marketdata.data.history + if result.orderType == 'Sell' + tr.danger + td.hidden-xs + =result.orderType + td + =result.quantity.toFixed(8).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1") + td + =result.price.toFixed(8) + td + =result.total.toFixed(8).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1") + td.hidden-xs + =result.timeStamp.substr(0, 19).replace("T", " ") + else + tr.success + td.hidden-xs + =result.orderType + td + =result.quantity.toFixed(8).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1") + td + =result.price.toFixed(8) + td + =result.total.toFixed(8).replace(/(?:\.0+|(\.\d+?)0+)$/, "$1") + td.hidden-xs + =result.timeStamp.substr(0, 19).replace("T", " ") + .footer-padding diff --git a/views/movement.jade b/views/movement.jade index 199a49574..8395ef191 100644 --- a/views/movement.jade +++ b/views/movement.jade @@ -2,6 +2,20 @@ extends layout block content script. + var setting_maxTxCount = parseInt("#{settings.index.last_txs}"); + var setting_txPerPage = parseInt("#{settings.index.txs_per_page}"); + var lengthMenuOpts = []; + var lengthMenuOptsAdd = [ 10, 25, 50, 75, 100, 250, 500, 1000 ]; + for (i=0; i < lengthMenuOptsAdd.length; i++) { + if (setting_maxTxCount >= lengthMenuOptsAdd[i]) { + lengthMenuOpts.push(lengthMenuOptsAdd[i]); + } + } + if (setting_maxTxCount < setting_txPerPage) { + var displayLengthMax = setting_maxTxCount; + } else { + var displayLengthMax = setting_txPerPage; + } var rplot; var colors = [ "#0071bc" ]; $(document).ready(function(){ @@ -35,31 +49,33 @@ block content searching: false, ordering: false, //responsive: true, - lengthChange: false, + lengthChange: true, + processing: true, + serverSide: true, + iDisplayLength: displayLengthMax, + lengthMenu: lengthMenuOpts, //processing: true, - ajax: { - url: '/ext/getlasttxs/#{min_amount}', - dataSrc: function ( json ) { - for ( var i=0;i" + json.data[i]['txid'] + "" - var amount = json.data[i]['total'] / 100000000; - if (amount > '#{flagb}') { - json .data[i]['total'] = ""; - } else if (amount > '#{flaga}') { - json .data[i]['total'] = ""; - } else { - json .data[i]['total'] = ""; - } - } - return json.data; + ajax: '/ext/getlasttxsajax/#{min_amount}', + rowCallback: function(row, data, index) { + var blockindex = data[0]; //variables for better readability + var blockhash = data[1]; //variables for better readability + var txhash = data[2]; //variables for better readability + var outputs = data[3]; //variables for better readability + var amount = (data[4] / 100000000); //variables for better readability + var amountParts = amount.toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}).split('.'); + var amountStr = amountParts[0] + '.' + amountParts[1] + ''; + var timestamp = data[5]; //variables for better readability + if (amount > '#{flagb}') { + var total = ""; + } else if (amount > '#{flaga}') { + var total = ""; + } else { + var total = ""; } + $("td:eq(0)", row).html(timestamp); + $("td:eq(1)", row).html('' + txhash + ''); + $("td:eq(2)", row).html(total); }, - columns: [ - { data: 'timestamp', width: '25%' }, - { data: 'txid', width: '60%' }, - { data: 'total', width: '15%' }, - ] }); setInterval( function () { rtable.api().ajax.reload(null, false); diff --git a/views/network.jade b/views/network.jade index e6bf445a9..0b8f3d8f5 100644 --- a/views/network.jade +++ b/views/network.jade @@ -1,48 +1,17 @@ extends layout block content - script. - $(document).ready(function(){ - var ctable = $('#connections-table').dataTable( { - autoWidth: true, - searching: false, - ordering: false, - responsive: true, - lengthChange: true, - processing: true, - ajax: { - url: '/ext/connections', - dataSrc: function ( json ) { - /*for ( var i=0;i" + json.data[i]['txid'] + ""; - json.data[i]['blockindex'] = "" + json.data[i]['blockindex'] + ""; - var amount = json.data[i]['total'] / 100000000; - json.data[i]['total'] = amount.toFixed(8); - json.data[i]['recipients'] = json.data[i]['vout'].length; - }*/ - return json.data; - } - }, - columns: [ - { data: 'address', width: '25%' }, - { data: 'protocol', width: '25%' }, - { data: 'version', width:'25%' }, - { data: 'country', width: '25%'} - ] - }); - }); .col-md-12(style="margin-bottom: 4%") .row.text-center(style='margin-bottom:15px;') i #{settings.locale.net_warning} - .panel.panel-default - .panel-heading - strong #{settings.locale.net_connections} - table#connections-table.table.table-bordered.table-striped - thead - tr - th.text-center #{settings.locale.net_address} - th.text-center #{settings.locale.net_protocol} - th.text-center #{settings.locale.net_subversion} - th.text-center #{settings.locale.net_country} - tbody.text-center + .tabpanel + ul.nav.nav-tabs(role='tablist') + li.active(role='presentation') + a(href='#connections', aria-controls='connections', role='tab', data-toggle='tab') #{settings.locale.net_connections} + li(role='presentation') + a(href='#addnodes', aria-controls='addnodes', role='tab', data-toggle='tab') #{settings.locale.net_addnodes} + .tab-content + #connections.tabpanel.tab-pane.active + include ./includes/net_list.jade + #addnodes.tabpanel.tab-pane + include ./includes/net_addnodes.jade diff --git a/views/richlist.jade b/views/richlist.jade index c3a11daf3..28fe992d5 100644 --- a/views/richlist.jade +++ b/views/richlist.jade @@ -64,25 +64,29 @@ block content th div.pull-left(style='background-color:#d9534f;width:20px;height:20px;margin-right:6px;') span #{settings.locale.rl_top25} - td #{parseFloat(dista.total).toFixed(8)} + - var total = parseFloat(dista.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + td #{total} td.text-center #{parseFloat(dista.percent).toFixed(2)} tr th div.pull-left(style='background-color:#5cb85c;width:20px;height:20px;margin-right:6px;') span #{settings.locale.rl_top50} - td #{parseFloat(distb.total).toFixed(8)} + - var total = parseFloat(distb.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + td #{total} td.text-center #{parseFloat(distb.percent).toFixed(2)} tr th div.pull-left(style='background-color:#428bca;width:20px;height:20px;margin-right:6px;') span #{settings.locale.rl_top75} - td #{parseFloat(distc.total).toFixed(8)} + - var total = parseFloat(distc.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + td #{total} td.text-center #{parseFloat(distc.percent).toFixed(2)} tr th div.pull-left(style='background-color:#222;width:20px;height:20px;margin-right:6px;') span #{settings.locale.rl_top100} - td #{parseFloat(distd.total).toFixed(8)} + - var total = parseFloat(distd.total).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + td #{total} td.text-center #{parseFloat(distd.percent).toFixed(2)} tr th diff --git a/views/tx.jade b/views/tx.jade index e641141a7..2d96c1953 100644 --- a/views/tx.jade +++ b/views/tx.jade @@ -71,14 +71,17 @@ block content tr.info(style='text-align:center') td #{settings.locale.new_coins} else - - var ramount = r.amount / 100000000 + - var ramount = (r.amount / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + - var ramountParts = ramount.split('.') tr td a.loading(href='/address/#{r.addresses}') =r.addresses - td.danger.hidden-xs #{ramount.toFixed(8)} + td.danger.hidden-xs #{ramountParts[0]}. + span.decimal #{ramountParts[1]} tr.hidden-lg.hidden-md - td.danger #{ramount.toFixed(8)} #{settings.symbol} + td.danger #{ramountParts[0]}. + span.decimal #{ramountParts[1]} #{settings.symbol} else tr.info(style='text-align:center') td #{settings.locale.proof_of_stake} @@ -94,20 +97,27 @@ block content tbody each r in tx.vout if tx.vout.length > 0 - - var ramount = r.amount / 100000000 + - var ramount = (r.amount / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + - var ramountParts = ramount.split('.') tr td a.loading(href='/address/#{r.addresses}') =r.addresses - td.success.hidden-xs #{ramount.toFixed(8)} + td.success.hidden-xs #{ramountParts[0]}. + span.decimal #{ramountParts[1]} tr.hidden-lg.hidden-md - td.success #{ramount.toFixed(8)} #{settings.symbol} + td.success #{ramountParts[0]}. + span.decimal #{ramountParts[1]} #{settings.symbol} else + - var ramount = (r.amount / 100000000).toLocaleString('en',{'minimumFractionDigits':2,'maximumFractionDigits':8,'useGrouping':true}) + - var ramountParts = ramount.split('.') tr td a.loading(href='/address/#{r.addresses}') =r.addresses - td.success.hidden-xs #{ramount.toFixed(8)} + td.success.hidden-xs #{ramountParts[0]}. + span.decimal #{ramountParts[1]} tr.hidden-lg.hidden-md - td.success #{ramount.toFixed(8)} #{settings.symbol} + td.success #{ramountParts[0]}. + span.decimal #{ramountParts[1]} #{settings.symbol} .footer-padding