@@ -0,0 +1,59 @@ | |||
# Logs | |||
logs | |||
*.log | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
# Runtime data | |||
pids | |||
*.pid | |||
*.seed | |||
*.pid.lock | |||
# Directory for instrumented libs generated by jscoverage/JSCover | |||
lib-cov | |||
# Coverage directory used by tools like istanbul | |||
coverage | |||
# nyc test coverage | |||
.nyc_output | |||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | |||
.grunt | |||
# Bower dependency directory (https://bower.io/) | |||
bower_components | |||
# node-waf configuration | |||
.lock-wscript | |||
# Compiled binary addons (http://nodejs.org/api/addons.html) | |||
build/Release | |||
# Dependency directories | |||
node_modules/ | |||
jspm_packages/ | |||
# Typescript v1 declaration files | |||
typings/ | |||
# Optional npm cache directory | |||
.npm | |||
# Optional eslint cache | |||
.eslintcache | |||
# Optional REPL history | |||
.node_repl_history | |||
# Output of 'npm pack' | |||
*.tgz | |||
# Yarn Integrity file | |||
.yarn-integrity | |||
# dotenv environment variables file | |||
.env | |||
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2017 Dan Janosik | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,15 @@ | |||
Simple, stateless Vivocoin blockchain explorer, via RPC. Built with Node.js, express, bootstrap-v4. | |||
This tool is intended to be a simple, stateless, self-hosted explorer for the Vivocoin blockchain, driven by RPC calls to your own smartcashd node. This tool is easy to run but lacks features compared to full-fledged (stateful) explorers. | |||
I built this tool because I wanted to use it myself. Whatever reasons one might have for running a full node (trustlessness, technical curiosity, etc) it's helpful to appreciate the "fullness" of a node. | |||
# Features | |||
* List of recent blocks | |||
* Browse blocks by height, in ascending or descending order | |||
* View block details | |||
* View transaction details, with navigation backward via spent outputs | |||
* View raw JSON output used to generate most pages | |||
* Mempool/unconfirmed transaction counts by fee (sat/B) |
@@ -0,0 +1,131 @@ | |||
#!/usr/bin/env node | |||
'use strict'; | |||
var express = require('express'); | |||
var path = require('path'); | |||
var favicon = require('serve-favicon'); | |||
var logger = require('morgan'); | |||
var cookieParser = require('cookie-parser'); | |||
var bodyParser = require('body-parser'); | |||
var session = require("express-session"); | |||
var env = require("./app/env.js"); | |||
var simpleGit = require('simple-git'); | |||
var utils = require("./app/utils.js"); | |||
var moment = require("moment"); | |||
var Decimal = require('decimal.js'); | |||
var bitcoin = require("bitcoin"); | |||
var momentDurationFormat = require("moment-duration-format"); | |||
var baseActionsRouter = require('./routes/baseActionsRouter'); | |||
var app = express(); | |||
// view engine setup | |||
app.set('views', path.join(__dirname, 'views')); | |||
app.set('view engine', 'pug'); | |||
// uncomment after placing your favicon in /public | |||
//app.use(favicon(__dirname + '/public/favicon.ico')); | |||
app.use(logger('dev')); | |||
app.use(bodyParser.json()); | |||
app.use(bodyParser.urlencoded({ extended: false })); | |||
app.use(cookieParser()); | |||
app.use(session({ | |||
secret: env.cookiePassword, | |||
resave: false, | |||
saveUninitialized: false | |||
})); | |||
app.use(express.static(path.join(__dirname, 'public'))); | |||
// Make our db accessible to our router | |||
app.use(function(req, res, next) { | |||
// make session available in templates | |||
res.locals.session = req.session; | |||
res.locals.debug = env.debug; | |||
if (env.smartcashd && env.smartcashd.rpc) { | |||
req.session.host = env.smartcashd.host; | |||
req.session.port = env.smartcashd.port; | |||
req.session.username = env.smartcashd.rpc.username; | |||
global.client = new bitcoin.Client({ | |||
host: env.smartcashd.host, | |||
port: env.smartcashd.port, | |||
user: env.smartcashd.rpc.username, | |||
pass: env.smartcashd.rpc.password, | |||
timeout: 5000 | |||
}); | |||
} | |||
res.locals.host = req.session.host; | |||
res.locals.port = req.session.port; | |||
/* if (!["/", "/connect"].includes(req.originalUrl)) { | |||
if (utils.redirectToConnectPageIfNeeded(req, res)) { | |||
return; | |||
} | |||
} | |||
*/ | |||
if (req.session.userMessage) { | |||
res.locals.userMessage = req.session.userMessage; | |||
if (req.session.userMessageType) { | |||
res.locals.userMessageType = req.session.userMessageType; | |||
} else { | |||
res.locals.userMessageType = "info"; | |||
} | |||
} | |||
req.session.userMessage = null; | |||
req.session.userMessageType = null; | |||
// make some var available to all request | |||
// ex: req.cheeseStr = "cheese"; | |||
next(); | |||
}); | |||
app.use('/', baseActionsRouter); | |||
/// catch 404 and forwarding to error handler | |||
app.use(function(req, res, next) { | |||
var err = new Error('Not Found'); | |||
err.status = 404; | |||
next(err); | |||
}); | |||
/// error handlers | |||
// development error handler | |||
// will print stacktrace | |||
if (app.get('env') === 'development') { | |||
app.use(function(err, req, res, next) { | |||
res.status(err.status || 500); | |||
res.render('error', { | |||
message: err.message, | |||
error: err | |||
}); | |||
}); | |||
} | |||
// production error handler | |||
// no stacktraces leaked to user | |||
app.use(function(err, req, res, next) { | |||
res.status(err.status || 500); | |||
res.render('error', { | |||
message: err.message, | |||
error: {} | |||
}); | |||
}); | |||
app.locals.moment = moment; | |||
app.locals.Decimal = Decimal; | |||
app.locals.utils = utils; | |||
module.exports = app; |
@@ -0,0 +1,16 @@ | |||
module.exports = { | |||
cookiePassword: "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", | |||
debug: false, | |||
// Uncomment "smartcashd" below to automatically connect via RPC. | |||
// Otherwise, you can manually connect via the UI. | |||
smartcashd:{ | |||
host:"127.0.0.1", | |||
port:5567, | |||
rpc: { | |||
username:"vivorpc", | |||
password:"c46d58eb99da24bb0392875f" | |||
} | |||
} | |||
}; |
@@ -0,0 +1,446 @@ | |||
var fs = require('fs'); | |||
var stream = fs.createWriteStream("proglog.txt"); | |||
var utils = require("./utils.js"); | |||
var genesisCoinbaseTransactionTxid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; | |||
var genesisCoinbaseTransaction = { | |||
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d02fd04ffffffff0100f2052a01000000434104f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446aac00000000", | |||
"txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", | |||
"hash": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", | |||
"size": 204, | |||
"vsize": 204, | |||
"version": 1, | |||
"confirmations":475000, | |||
"vin": [ | |||
{ | |||
"coinbase": "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", | |||
"sequence": 4294967295 | |||
} | |||
], | |||
"vout": [ | |||
{ | |||
"value": 50, | |||
"n": 0, | |||
"scriptPubKey": { | |||
"asm": "04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a OP_CHECKSIG", | |||
"hex": "4104f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446aac", | |||
"reqSigs": 1, | |||
"type": "pubkey", | |||
"addresses": [ | |||
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" | |||
] | |||
} | |||
} | |||
], | |||
"blockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", | |||
"time": 1230988505, | |||
"blocktime": 1230988505 | |||
}; | |||
function getInfo() { | |||
return new Promise(function(resolve, reject) { | |||
client.cmd('getinfo', function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error 3207fh0f: " + err); | |||
reject(err); | |||
return; | |||
} | |||
resolve(result); | |||
}); | |||
}); | |||
} | |||
function getMiningInfo() { | |||
return new Promise(function(resolve, reject) { | |||
client.cmd('getmininginfo', function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error 3207fh0f: " + err); | |||
reject(err); | |||
return; | |||
} | |||
resolve(result); | |||
}); | |||
}); | |||
} | |||
function getMempoolInfo() { | |||
return new Promise(function(resolve, reject) { | |||
client.cmd('getmempoolinfo', function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error 23407rhwe07fg: " + err); | |||
reject(err); | |||
return; | |||
} | |||
resolve(result); | |||
}); | |||
}); | |||
} | |||
function getMempoolStats() { | |||
return new Promise(function(resolve, reject) { | |||
client.cmd('getrawmempool', true, function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error 428thwre0ufg: " + err); | |||
reject(err); | |||
return; | |||
} | |||
var compiledResult = {}; | |||
compiledResult.count = 0; | |||
compiledResult.fee_0_5 = 0; | |||
compiledResult.fee_6_10 = 0; | |||
compiledResult.fee_11_25 = 0; | |||
compiledResult.fee_26_50 = 0; | |||
compiledResult.fee_51_75 = 0; | |||
compiledResult.fee_76_100 = 0; | |||
compiledResult.fee_101_150 = 0; | |||
compiledResult.fee_151_max = 0; | |||
var totalFee = 0; | |||
for (var txid in result) { | |||
var txMempoolInfo = result[txid]; | |||
totalFee += txMempoolInfo.modifiedfee; | |||
var feeRate = Math.round(txMempoolInfo.modifiedfee * 100000000 / txMempoolInfo.size); | |||
if (feeRate <= 5) { | |||
compiledResult.fee_0_5++; | |||
} else if (feeRate <= 10) { | |||
compiledResult.fee_6_10++; | |||
} else if (feeRate <= 25) { | |||
compiledResult.fee_11_25++; | |||
} else if (feeRate <= 50) { | |||
compiledResult.fee_26_50++; | |||
} else if (feeRate <= 75) { | |||
compiledResult.fee_51_75++; | |||
} else if (feeRate <= 100) { | |||
compiledResult.fee_76_100++; | |||
} else if (feeRate <= 150) { | |||
compiledResult.fee_101_150++; | |||
} else { | |||
compiledResult.fee_151_max++; | |||
} | |||
compiledResult.count++; | |||
} | |||
compiledResult.totalFee = totalFee; | |||
resolve(compiledResult); | |||
}); | |||
}); | |||
} | |||
function getBlockByHeight(blockHeight) { | |||
console.log("getBlockByHeightzz: " + blockHeight); | |||
console.log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxx \n"); | |||
//************************************************************************************* | |||
stream.write('block:'+blockHeight); | |||
return new Promise(function(resolve, reject) { | |||
var client = global.client; | |||
client.cmd('getblockhash', blockHeight, function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error 0928317yr3w: " + err); | |||
reject(err); | |||
return; | |||
} | |||
client.cmd('getblock', result, function(err2, result2, resHeaders2) { | |||
if (err2) { | |||
console.log("Error 320fh7e0hg: " + err2); | |||
reject(err2); | |||
return; | |||
} | |||
resolve({ success:true, getblockhash:result, getblock:result2 }); | |||
}); | |||
}); | |||
}); | |||
} | |||
function getBlocksByHeight(blockHeights) { | |||
console.log("getBlocksByHeight: " + blockHeights); | |||
console.log("getBlockByHeightzz: " + blockHeights); | |||
console.log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxx \n"); | |||
//************************************************************************************* | |||
stream.write('block:'+blockHeights+"\n"); | |||
return new Promise(function(resolve, reject) { | |||
var batch = []; | |||
for (var i = 0; i < blockHeights.length; i++) { | |||
batch.push({ | |||
method: 'getblockhash', | |||
params: [ blockHeights[i] ] | |||
}); | |||
} | |||
var blockHashes = []; | |||
client.cmd(batch, function(err, result, resHeaders) { | |||
blockHashes.push(result); | |||
if (blockHashes.length == batch.length) { | |||
var batch2 = []; | |||
for (var i = 0; i < blockHashes.length; i++) { | |||
batch2.push({ | |||
method: 'getblock', | |||
params: [ blockHashes[i] ] | |||
}); | |||
} | |||
var blocks = []; | |||
client.cmd(batch2, function(err2, result2, resHeaders2) { | |||
if (err2) { | |||
console.log("Error 138ryweufdf: " + err2); | |||
} | |||
blocks.push(result2); | |||
if (blocks.length == batch2.length) { | |||
resolve(blocks); | |||
} | |||
}); | |||
} | |||
}); | |||
}); | |||
} | |||
function getBlockByHash(blockHash) { | |||
console.log("getBlockByHash: " + blockHash); | |||
return new Promise(function(resolve, reject) { | |||
var client = global.client; | |||
client.cmd('getblock', blockHash, function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error 0u2fgewue: " + err); | |||
reject(err); | |||
return; | |||
} | |||
resolve(result); | |||
}); | |||
}); | |||
} | |||
function getTransactionInputs(rpcClient, transaction, inputLimit=0) { | |||
console.log("getTransactionInputs: " + transaction.txid); | |||
return new Promise(function(resolve, reject) { | |||
var txids = []; | |||
for (var i = 0; i < transaction.vin.length; i++) { | |||
if (i < inputLimit || inputLimit == 0) { | |||
txids.push(transaction.vin[i].txid); | |||
} | |||
} | |||
getRawTransactions(txids).then(function(inputTransactions) { | |||
resolve({ txid:transaction.txid, inputTransactions:inputTransactions }); | |||
}); | |||
}); | |||
} | |||
function getRawTransaction(txid) { | |||
return new Promise(function(resolve, reject) { | |||
if (txid == genesisCoinbaseTransactionTxid) { | |||
getBlockByHeight(0).then(function(blockZeroResult) { | |||
var result = genesisCoinbaseTransaction; | |||
result.confirmations = blockZeroResult.getblock.confirmations; | |||
resolve(result); | |||
}); | |||
return; | |||
} | |||
client.cmd('getrawtransaction', txid, 1, function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error 329813yre823: " + err); | |||
reject(err); | |||
return; | |||
} | |||
resolve(result); | |||
}); | |||
}); | |||
} | |||
function getRawTransactions(txids) { | |||
console.log("getRawTransactions: " + txids); | |||
return new Promise(function(resolve, reject) { | |||
if (!txids || txids.length == 0) { | |||
resolve([]); | |||
return; | |||
} | |||
if (txids.length == 1 && txids[0] == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b") { | |||
// copy the "confirmations" field from genesis block to the genesis-coinbase tx | |||
getBlockByHeight(0).then(function(blockZeroResult) { | |||
var result = genesisCoinbaseTransaction; | |||
result.confirmations = blockZeroResult.getblock.confirmations; | |||
resolve([result]); | |||
}).catch(function(err) { | |||
reject(err); | |||
return; | |||
}); | |||
return; | |||
} | |||
var requests = []; | |||
for (var i = 0; i < txids.length; i++) { | |||
var txid = txids[i]; | |||
if (txid) { | |||
requests.push({ | |||
method: 'getrawtransaction', | |||
params: [ txid, 1 ] | |||
}); | |||
} | |||
} | |||
var requestBatches = utils.splitArrayIntoChunks(requests, 20); | |||
executeBatchesSequentially(requestBatches, function(results) { | |||
resolve(results); | |||
}); | |||
}); | |||
} | |||
function executeBatchesSequentially(batches, resultFunc) { | |||
var batchId = utils.getRandomString(20, 'aA#'); | |||
console.log("Starting " + batches.length + "-item batch " + batchId + "..."); | |||
executeBatchesSequentiallyInternal(batchId, batches, 0, [], resultFunc); | |||
} | |||
function executeBatchesSequentiallyInternal(batchId, batches, currentIndex, accumulatedResults, resultFunc) { | |||
if (currentIndex == batches.length) { | |||
console.log("Finishing batch " + batchId + "..."); | |||
resultFunc(accumulatedResults); | |||
return; | |||
} | |||
console.log("Executing item #" + (currentIndex + 1) + " (of " + batches.length + ") for batch " + batchId); | |||
var count = batches[currentIndex].length; | |||
client.cmd(batches[currentIndex], function(err, result, resHeaders) { | |||
if (err) { | |||
console.log("Error f83024hf4: " + err); | |||
} | |||
accumulatedResults.push(result); | |||
count--; | |||
if (count == 0) { | |||
executeBatchesSequentiallyInternal(batchId, batches, currentIndex + 1, accumulatedResults, resultFunc); | |||
} | |||
}); | |||
} | |||
function getBlockData(rpcClient, blockHash, txLimit, txOffset) { | |||
console.log("getBlockData: " + blockHash); | |||
return new Promise(function(resolve, reject) { | |||
client.cmd('getblock', blockHash, function(err2, result2, resHeaders2) { | |||
if (err2) { | |||
console.log("Error 3017hfwe0f: " + err2); | |||
reject(err2); | |||
return; | |||
} | |||
var txids = []; | |||
for (var i = txOffset; i < Math.min(txOffset + txLimit, result2.tx.length); i++) { | |||
txids.push(result2.tx[i]); | |||
} | |||
getRawTransactions(txids).then(function(transactions) { | |||
var txInputsByTransaction = {}; | |||
var promises = []; | |||
for (var i = 0; i < transactions.length; i++) { | |||
var transaction = transactions[i]; | |||
if (transaction) { | |||
promises.push(getTransactionInputs(client, transaction, 10)); | |||
} | |||
} | |||
Promise.all(promises).then(function() { | |||
var results = arguments[0]; | |||
for (var i = 0; i < results.length; i++) { | |||
var resultX = results[i]; | |||
txInputsByTransaction[resultX.txid] = resultX.inputTransactions; | |||
} | |||
resolve({ getblock:result2, transactions:transactions, txInputsByTransaction:txInputsByTransaction }); | |||
}); | |||
}); | |||
}); | |||
}); | |||
} | |||
module.exports = { | |||
getInfo: getInfo, | |||
getMiningInfo: getMiningInfo, | |||
getMempoolInfo: getMempoolInfo, | |||
getBlockByHeight: getBlockByHeight, | |||
getBlocksByHeight: getBlocksByHeight, | |||
getBlockByHash: getBlockByHash, | |||
getTransactionInputs: getTransactionInputs, | |||
getBlockData: getBlockData, | |||
getRawTransaction: getRawTransaction, | |||
getRawTransactions: getRawTransactions, | |||
getMempoolStats: getMempoolStats | |||
}; |
@@ -0,0 +1,103 @@ | |||
var Decimal = require("decimal.js"); | |||
Decimal8 = Decimal.clone({ precision:8, rounding:8 }); | |||
function doSmartRedirect(req, res, defaultUrl) { | |||
if (req.session.redirectUrl) { | |||
res.redirect(req.session.redirectUrl); | |||
req.session.redirectUrl = null; | |||
} else { | |||
res.redirect(defaultUrl); | |||
} | |||
res.end(); | |||
} | |||
function redirectToConnectPageIfNeeded(req, res) { | |||
if (!req.session.host) { | |||
req.session.redirectUrl = req.originalUrl; | |||
res.redirect("/"); | |||
res.end(); | |||
return true; | |||
} | |||
return false; | |||
} | |||
function hex2ascii(hex) { | |||
var str = ""; | |||
for (var i = 0; i < hex.length; i += 2) { | |||
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); | |||
} | |||
return str; | |||
} | |||
function getBlockReward(blockHeight) { | |||
var nSubsidy = 10 * 100000000; | |||
var prevBlock = blockHeight -1; | |||
// yearly decline of production by 10% per year, projected 136m coins max by year 2050+. | |||
var i =0; | |||
for (i = 262800; i <= prevBlock ; i += 262800 ) { | |||
nSubsidy -= nSubsidy/10; | |||
} | |||
return nSubsidy/100000000; | |||
} | |||
function splitArrayIntoChunks(array, chunkSize) { | |||
var j = array.length; | |||
var chunks = []; | |||
for (var i = 0; i < j; i += chunkSize) { | |||
chunks.push(array.slice(i, i + chunkSize)); | |||
} | |||
return chunks; | |||
} | |||
function getRandomString(length, chars) { | |||
var mask = ''; | |||
if (chars.indexOf('a') > -1) { | |||
mask += 'abcdefghijklmnopqrstuvwxyz'; | |||
} | |||
if (chars.indexOf('A') > -1) { | |||
mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |||
} | |||
if (chars.indexOf('#') > -1) { | |||
mask += '0123456789'; | |||
} | |||
if (chars.indexOf('!') > -1) { | |||
mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; | |||
} | |||
var result = ''; | |||
for (var i = length; i > 0; --i) { | |||
result += mask[Math.floor(Math.random() * mask.length)]; | |||
} | |||
return result; | |||
} | |||
module.exports = { | |||
doSmartRedirect: doSmartRedirect, | |||
redirectToConnectPageIfNeeded: redirectToConnectPageIfNeeded, | |||
hex2ascii: hex2ascii, | |||
getBlockReward: getBlockReward, | |||
splitArrayIntoChunks: splitArrayIntoChunks, | |||
getRandomString: getRandomString | |||
}; |
@@ -0,0 +1,31 @@ | |||
#!/usr/bin/env node | |||
var debug = require('debug')('my-application'); | |||
var app = require('../app'); | |||
var http = require('http'); | |||
var https = require('https'); | |||
var fs = require('fs'); | |||
// Https run | |||
/* | |||
const httpsOptions = { | |||
cert: fs.readFileSync('/etc/letsencrypt/live/rpcexplorer.smartcash.cc/fullchain.pem'), | |||
key: fs.readFileSync('/etc/letsencrypt/live/rpcexplorer.smartcash.cc/privkey.pem') | |||
} | |||
https.createServer(httpsOptions, app).listen(443, function() { | |||
debug('Https/Express server listening on port 443'); | |||
}); | |||
// Forwarding http to https | |||
http.createServer(function (req, res) { | |||
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url }); | |||
res.end(); | |||
}).listen(80); | |||
*/ | |||
// Http run | |||
app.set('port', process.env.PORT || 80); | |||
var server = app.listen(app.get('port'), function() { | |||
debug('Express server listening on port ' + server.address().port); | |||
}); | |||
@@ -0,0 +1,22 @@ | |||
### Setup of https://btc-explorer.com on Ubuntu 16.04 | |||
apt update | |||
apt upgrade | |||
apt install git python-software-properties software-properties-common nginx | |||
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - | |||
npm install pm2 --global | |||
add-apt-repository ppa:certbot/certbot | |||
apt update | |||
apt upgrade | |||
apt install python-certbot-nginx | |||
Copy content from [./btc-explorer.com.conf](./btc-explorer.com.conf) into `/etc/nginx/sites-available/btc-explorer.com.conf` | |||
certbot --nginx -d btc-explorer.com | |||
cd /etc/ssl/certs | |||
openssl dhparam -out dhparam.pem 4096 | |||
cd /home/bitcoin | |||
git clone https://github.com/janoside/btc-rpc-explorer.git | |||
cd /home/bitcoin/btc-rpc-explorer | |||
npm install | |||
pm2 start bin/www --name "btc-rpc-explorer" |
@@ -0,0 +1,32 @@ | |||
## http://domain.com redirects to https://domain.com | |||
server { | |||
server_name btc-explorer.com; | |||
listen 80; | |||
#listen [::]:80 ipv6only=on; | |||
location / { | |||
return 301 https://btc-explorer.com$request_uri; | |||
} | |||
} | |||
## Serves httpS://domain.com | |||
server { | |||
server_name btc-explorer.com; | |||
listen 443 ssl http2; | |||
#listen [::]:443 ssl http2 ipv6only=on; | |||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; | |||
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; | |||
ssl_prefer_server_ciphers on; | |||
ssl_session_cache shared:SSL:10m; | |||
ssl_dhparam /etc/ssl/certs/dhparam.pem; | |||
location / { | |||
proxy_pass http://localhost:3002; | |||
proxy_http_version 1.1; | |||
proxy_set_header Upgrade $http_upgrade; | |||
proxy_set_header Connection 'upgrade'; | |||
proxy_set_header Host $host; | |||
proxy_cache_bypass $http_upgrade; | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
{ | |||
"name": "rpc-explorer", | |||
"version": "1.0.0", | |||
"scripts": { | |||
"start": "node ./bin/www" | |||
}, | |||
"repository": { | |||
"type": "git", | |||
"url": "https://github.com/smartcash/rpc-explorer.git" | |||
}, | |||
"dependencies": { | |||
"bitcoin": "3.0.1", | |||
"body-parser": "~1.16.0", | |||
"cookie-parser": "~1.4.3", | |||
"crypto-js": "3.1.9-1", | |||
"debug": "~2.6.0", | |||
"decimal.js": "7.2.3", | |||
"express": "~4.14.1", | |||
"express-session": "1.15.2", | |||
"jstransformer-markdown-it": "^2.0.0", | |||
"moment": "^2.18.1", | |||
"moment-duration-format": "1.3.0", | |||
"monk": "^4.0.0", | |||
"morgan": "~1.7.0", | |||
"pug": "2.0.0-rc.2", | |||
"serve-favicon": "~2.3.2", | |||
"simple-git": "1.73.0" | |||
}, | |||
"license": "MIT" | |||
} |
@@ -0,0 +1,113 @@ | |||
block:896936,896935,896934,896933,896932,896931,896930,896929,896928,896927 | |||
block:896936,896935,896934,896yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3475689.919block:8yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3620550.313359255 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3097414.750693175 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3097414.750693175 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3097414.750693175 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3063122.423659674 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3064355.489685315 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3064355.489685315 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3025464.432089422 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:3025464.432089422 | |||
mininghash:3025464.432089422 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:2373157.008316292 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:2425140.595686032 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:2363615.794209859 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:2297304.049850194 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:2109127.042296987 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:2109127.042296987 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:1887491.861699247 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:1887491.861699247 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:1806276.69781136 | |||
yyyyyyyyyyyyyyyyyyyyyyy | |||
mininghash:1751051.827217125 | |||
009,897008,897007 | |||
block:897062,897061,897060,897059,897058,897057,897056,897055,897054,897053 | |||
block:897132,897131,897130,897129,897128,897127,897126,897125,897124,897123 | |||
block:897155,897154,897153,897152,897151,897150,897149,897148,897147,897146 | |||
block:897194,897193,897192,897191,897190,897189,897188,897187,897186,897185 | |||
block:897196,897195,897194,897193,897192,897191,897190,897189,897188,897187 | |||
block:897197,897196,897195,897194,897193,897192,897191,897190,897189,897188,897187,897186,897185,897184,897183,897182,897181,897180,897179,897178 | |||
block:897177,897176,897175,897174,897173,897172,897171,897170,897169,897168,897167,897166,897165,897164,897163,897162,897161,897160,897159,897158 | |||
block:897157,897156,897155,897154,897153,897152,897151,897150,897149,897148,897147,897146,897145,897144,897143,897142,897141,897140,897139,897138 | |||
block:897117,897116,897115,897114,897113,897112,897111,897110,897109,897108,897107,897106,897105,897104,897103,897102,897101,897100,897099,897098 | |||
block:897097,897096,897095,897094,897093,897092,897091,897090,897089,897088,897087,897086,897085,897084,897083,897082,897081,897080,897079,897078 | |||
block:897077,897076,897075,897074,897073,897072,897071,897070,897069,897068,897067,897066,897065,897064,897063,897062,897061,897060,897059,897058 | |||
block:897057,897056,897055,897054,897053,897052,897051,897050,897049,897048,897047,897046,897045,897044,897043,897042,897041,897040,897039,897038 | |||
block:897037,897036,897035,897034,897033,897032,897031,897030,897029,897028,897027,897026,897025,897024,897023,897022,897021,897020,897019,897018 | |||
block:897017,897016,897015,897014,897013,897012,897011,897010,897009,897008,897007,897006,897005,897004,897003,897002,897001,897000,896999,896998 | |||
block:896997,896996,896995,896994,896993,896992,896991,896990,896989,896988,896987,896986,896985,896984,896983,896982,896981,896980,896979,896978 | |||
block:896977,896976,896975,896974,896973,896972,896971,896970,896969,896968,896967,896966,896965,896964,896963,896962,896961,896960,896959,896958 | |||
block:896957,896956,896955,896954,896953,896952,896951,896950,896949,896948,896947,896946,896945,896944,896943,896942,896941,896940,896939,896938 | |||
block:896937,896936,896935,896934,896933,896932,896931,896930,896929,896928,896927,896926,896925,896924,896923,896922,896921,896920,896919,896918 | |||
block:896917,896916,896915,896914,896913,896912,896911,896910,896909,896908,896907,896906,896905,896904,896903,896902,896901,896900,896899,896898 | |||
block:896897,896896,896895,896894,896893,896892,896891,896890,896889,896888,896887,896886,896885,896884,896883,896882,896881,896880,896879,896878 | |||
block:897197,897196,897195,897194,897193,897192,897191,897190,897189,897188 | |||
block:897197,897196,897195,897194,897193,897192,897191,897190,897189,897188,897187,897186,897185,897184,897183,897182,897181,897180,897179,897178 | |||
block:897197,897196,897195,897194,897193,897192,897191,897190,897189,897188 | |||
block:897200,897199,897198,897197,897196,897195,897194,897193,897192,897191 | |||
block:897201,897200,897199,897198,897197,897196,897195,897194,897193,897192 | |||
block:897203,897202,897201,897200,897199,897198,897197,897196,897195,897194 | |||
block:897206,897205,897204,897203,897202,897201,897200,897199,897198,897197 | |||
block:897206,897205,897204,897203,897202,897201,897200,897199,897198,897197,897196,897195,897194,897193,897192,897191,897190,897189,897188,897187 | |||
block:897208,897207,897206,897205,897204,897203,897202,897201,897200,897199 | |||
block:897209,897208,897207,897206,897205,897204,897203,897202,897201,897200 | |||
block:897211,897210,897209,897208,897207,897206,897205,897204,897203,897202 | |||
block:897215,897214,897213,897212,897211,897210,897209,897208,897207,897206 | |||
block:897242,897241,897240,897239,897238,897237,897236,897235,897234,897233 | |||
block:897258,897257,897256,897255,897254,897253,897252,897251,897250,897249 | |||
block:897258,897257,897256,897255,897254,897253,897252,897251,897250,897249 | |||
block:897258,897257,897256,897255,897254,897253,897252,897251,897250,897249 | |||
block:897258,897257,897256,897255,897254,897253,897252,897251,897250,897249,897248,897247,897246,897245,897244,897243,897242,897241,897240,897239 | |||
block:897218,897217,897216,897215,897214,897213,897212,897211,897210,897209,897208,897207,897206,897205,897204,897203,897202,897201,897200,897199 | |||
block:897259,897258,897257,897256,897255,897254,897253,897252,897251,897250 | |||
block:897259,897258,897257,897256,897255,897254,897253,897252,897251,897250 | |||
block:897262,897261,897260,897259,897258,897257,897256,897255,897254,897253 | |||
block:897262,897261,897260,897259,897258,897257,897256,897255,897254,897253 | |||
block:897262,897261,897260,897259,897258,897257,897256,897255,897254,897253 | |||
block:897262,897261,897260,897259,897258,897257,897256,897255,897254,897253 | |||
block:897262,897261,897260,897259,897258,897257,897256,897255,897254,897253 | |||
block:897262,897261,897260,897259,897258,897257,897256,897255,897254,897253 | |||
block:897262,897261,897260,897259,897258,897257,897256,897255,897254,897253 | |||
block:897263,897262,897261,897260,897259,897258,897257,897256,897255,897254 | |||
block:897265,897264,897263,897262,897261,897260,897259,897258,897257,897256 | |||
block:897265,897264,897263,897262,897261,897260,897259,897258,897257,897256 | |||
block:897265,897264,897263,897262,897261,897260,897259,897258,897257,897256 | |||
block:897265,897264,897263,897262,897261,897260,897259,897258,897257,897256 | |||
block:897271,897270,897269,897268,897267,897266,897265,897264,897263,897262 | |||
block:897272,897271,897270,897269,897268,897267,897266,897265,897264,897263 |
@@ -0,0 +1,179 @@ | |||
body { | |||
font: 14px 'Open Sans', "Lucida Grande", Helvetica, Arial, sans-serif; | |||
background-color: #F3FAED; | |||
} | |||
code { | |||
background-color: #C4F5EE; | |||
} | |||
hr { | |||
margin: 15px 0; | |||
} | |||
a { | |||
color: #16548A; | |||
font-weight: bold; | |||
} | |||
a:hover { | |||
color: #26296D; | |||
} | |||
::selection { | |||
background-color: #AE95F1; | |||
} | |||
thead { | |||
background-color: #9CFFF8!important; | |||
} | |||
.table td, .table th { | |||
border-top: 1px solid #4DA2AF; | |||
} | |||
img.header-image { | |||
margin-top: -10px; | |||
margin-bottom: -5px; | |||
width: 30px; | |||
height: 30px; | |||
margin-right: 10px; | |||
} | |||
code, .monospace { | |||
font-family: "Cousine", monospace; | |||
} | |||
.properties-header { | |||
width: 180px; | |||
text-align: right; | |||
} | |||
.word-wrap { | |||
word-wrap: break-word; | |||
word-break: break-all; | |||
} | |||
.tag { | |||
border-radius: 4px; | |||
background-color: #0275D8; | |||
color: white; | |||
padding: 2px 5px; | |||
margin-right: 4px; | |||
} | |||
#subheader a { | |||
margin-right: 20px; | |||
} | |||
.data-cell, .data-header { | |||
text-align: right; | |||
} | |||
.btn-primary { | |||
color: black; | |||
background-color: #73DFF5; | |||
border-color: #386CA0; | |||
} | |||
.btn-primary:hover { | |||
color: black; | |||
background-color: #43EFE4; | |||
border-color: #386CA0; | |||
} | |||
.btn-primary:active { | |||
color: white!important; | |||
background-color: #43EFE4!important; | |||
border-color: #386CA0!important; | |||
box-shadow: 0px 1px 1px #9CFFF8 inset, 2px 2px 4px #739AF5!important; | |||
} | |||
.btn-primary:focus { | |||
box-shadow: 0px 1px 1px #739AF5 inset, 2px 2px 4px #739AF5!important; | |||
} | |||
.bg-dark { | |||
background-color: #9CFFF8!important; | |||
} | |||
.navbar-brand { | |||
font-weight: bold; | |||
} | |||
.nav-link { | |||
border-color: #125DA7!important; | |||
font-weight: bold; | |||
} | |||
.nav-link:hover { | |||
background-color: #9CFFF8!important; | |||
} | |||
.nav-link.active { | |||
color: #420857!important; | |||
background-color: #9CFFF8!important; | |||
border-color: #125DA7!important; | |||
} | |||
.alert-info { | |||
background-color: #C55AF3; | |||
border-color: #739AF5; | |||
color: black; | |||
} | |||
.alert-success { | |||
background-color: #C4FF80; | |||
border-color: #5F8660; | |||
color: black; | |||
} | |||
.form-control { | |||
background-color: #D4EFFF; | |||
} | |||
.form-control:hover { | |||
background-color: #F3FAED; | |||
box-shadow: 0px 1px 1px #739AF5 inset, 2px 2px 4px #739AF5!important; | |||
} | |||
.form-control:focus { | |||
background-color: #F3FAED; | |||
box-shadow: 0px 1px 1px #739AF5 inset, 2px 2px 4px #739AF5!important; | |||
} | |||
.card { | |||
background-color: #D4EFFF; | |||
} | |||
.tag { | |||
color: black; | |||
background-color: #9CFFF8; | |||
} | |||
.page-link { | |||
color: #420857!important; | |||
border-color: #35769C!important; | |||
} | |||
.page-link:hover { | |||
background-color: #9CFFF8!important; | |||
box-shadow: 0px 1px 1px #739AF5 inset, 1px 1px 2px #739AF5!important; | |||
border-color: #125DA7!important; | |||
} | |||
.page-item.active .page-link { | |||
color: #420857!important; | |||
background-color: #9CFFF8!important; | |||
border-color: #125DA7!important; | |||
} | |||
.pagination-lg .page-link { | |||
padding: .25rem 0.75rem; | |||
font-size: 1.0rem; | |||
line-height: 1.5; | |||
} | |||
@@ -0,0 +1,9 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<browserconfig> | |||
<msapplication> | |||
<tile> | |||
<square150x150logo src="/public/img/favicons/mstile-150x150.png"/> | |||
<TileColor>#ffc40d</TileColor> | |||
</tile> | |||
</msapplication> | |||
</browserconfig> |
@@ -0,0 +1,19 @@ | |||
{ | |||
"name": "", | |||
"short_name": "", | |||
"icons": [ | |||
{ | |||
"src": "/public/img/favicons/android-chrome-192x192.png", | |||
"sizes": "192x192", | |||
"type": "image/png" | |||
}, | |||
{ | |||
"src": "/public/img/favicons/android-chrome-512x512.png", | |||
"sizes": "512x512", | |||
"type": "image/png" | |||
} | |||
], | |||
"theme_color": "#ffffff", | |||
"background_color": "#ffffff", | |||
"display": "standalone" | |||
} |
@@ -0,0 +1,367 @@ | |||
var express = require('express'); | |||
var router = express.Router(); | |||
var util = require('util'); | |||
var moment = require('moment'); | |||
var utils = require('./../app/utils'); | |||
var env = require("./../app/env"); | |||
var bitcoin = require("bitcoin"); | |||
var rpcApi = require("./../app/rpcApi"); | |||
var fs = require('fs'); | |||
var stream = fs.createWriteStream("proglog.txt"); | |||
router.get("/", function(req, res) { | |||
if (!req.session.host) { | |||
if (req.cookies['rpc-host']) { | |||
res.locals.host = req.cookies['rpc-host']; | |||
} | |||
if (req.cookies['rpc-port']) { | |||
res.locals.port = req.cookies['rpc-port']; | |||
} | |||
if (req.cookies['rpc-username']) { | |||
res.locals.username = req.cookies['rpc-username']; | |||
} | |||
res.render("connect"); | |||
res.end(); | |||
return; | |||
} | |||
var client = global.client; | |||
rpcApi.getInfo().then(function(getinfo) { | |||
res.locals.getinfo = getinfo; | |||
var blockHeights = []; | |||
if (getinfo.blocks) { | |||
for (var i = 0; i < 10; i++) { | |||
blockHeights.push(getinfo.blocks - i); | |||
} | |||
} | |||
rpcApi.getBlocksByHeight(blockHeights).then(function(latestBlocks) { | |||
res.locals.latestBlocks = latestBlocks; | |||
res.render("index"); | |||
}); | |||
}).catch(function(err) { | |||
console.log(err); | |||
res.locals.userMessage = "Unable to connect to Vivocoin Node at " + req.session.host + ":" + req.session.port; | |||
res.render("index"); | |||
}); | |||
}); | |||
router.get("/node-info", function(req, res) { | |||
var client = global.client; | |||
stream.write("yyyyyyyyyyyyyyyyyyyyyyy\n"); | |||
rpcApi.getInfo().then(function(getinfo) { | |||
res.locals.getinfo = getinfo; | |||
rpcApi.getMiningInfo().then(function(getmininginfo) { | |||
res.locals.getmininginfo = getmininginfo; | |||
console.log(getmininginfo); | |||
stream.write('mininghash:'+ getmininginfo.networkhashps +"\n"); | |||
res.render("node-info"); | |||
}); | |||
}).catch(function(err) { | |||
res.locals.userMessage = "Unable to connect to Vivocoin Node at " + req.session.host + ":" + req.session.port; | |||
console.log(err); | |||
res.render("node-info"); | |||
}); | |||
}); | |||
router.get("/mempool", function(req, res) { | |||
var client = global.client; | |||
rpcApi.getMempoolInfo().then(function(getmempoolinfo) { | |||
res.locals.getmempoolinfo = getmempoolinfo; | |||
rpcApi.getMempoolStats().then(function(mempoolstats) { | |||
res.locals.mempoolstats = mempoolstats; | |||
res.render("mempool"); | |||
}); | |||
}).catch(function(err) { | |||
res.locals.userMessage = "Unable to connect to Vivocoin Node at " + req.session.host + ":" + req.session.port; | |||
console.log(err); | |||
res.render("mempool"); | |||
}); | |||
}); | |||
router.post("/connect", function(req, res) { | |||
var host = req.body.host; | |||
var port = req.body.port; | |||
var username = req.body.username; | |||
var password = req.body.password; | |||
res.cookie('rpc-host', host); | |||
res.cookie('rpc-port', port); | |||
res.cookie('rpc-username', username); | |||
req.session.host = host; | |||
req.session.port = port; | |||
req.session.username = username; | |||
var client = new bitcoin.Client({ | |||
host: host, | |||
port: port, | |||
user: username, | |||
pass: password, | |||
timeout: 30000 | |||
}); | |||
console.log("created client: " + client); | |||
global.client = client; | |||
req.session.userMessage = "<strong>Connected via RPC</strong>: " + username + " @ " + host + ":" + port; | |||
req.session.userMessageType = "success"; | |||
res.redirect("/"); | |||
}); | |||
router.get("/blocks", function(req, res) { | |||
var limit = 20; | |||
var offset = 0; | |||
var sort = "desc"; | |||
if (req.query.limit) { | |||
limit = parseInt(req.query.limit); | |||
} | |||
if (req.query.offset) { | |||
offset = parseInt(req.query.offset); | |||
} | |||
if (req.query.sort) { | |||
sort = req.query.sort; | |||
} | |||
res.locals.limit = limit; | |||
res.locals.offset = offset; | |||
res.locals.sort = sort; | |||
res.locals.paginationBaseUrl = "/blocks"; | |||
rpcApi.getInfo().then(function(getinfo) { | |||
res.locals.blockCount = getinfo.blocks; | |||
res.locals.blockOffset = offset; | |||
var blockHeights = []; | |||
if (sort == "desc") { | |||
for (var i = (getinfo.blocks - offset); i > (getinfo.blocks - offset - limit); i--) { | |||
blockHeights.push(i); | |||
} | |||
} else { | |||
for (var i = offset; i < (offset + limit); i++) { | |||
blockHeights.push(i); | |||
} | |||
} | |||
rpcApi.getBlocksByHeight(blockHeights).then(function(blocks) { | |||
res.locals.blocks = blocks; | |||
res.render("blocks"); | |||
}); | |||
}).catch(function(err) { | |||
res.locals.userMessage = "Unable to connect to Vivocoin Node at " + req.session.host + ":" + req.session.port; | |||
console.log(err); | |||
res.render("blocks"); | |||
}); | |||
}); | |||
router.post("/search", function(req, res) { | |||
if (!req.body.query) { | |||
req.session.userMessage = "Enter a block height, block hash, or transaction id."; | |||
res.redirect("/"); | |||
return; | |||
} | |||
var query = req.body.query.toLowerCase(); | |||
rpcApi.getRawTransaction(query).then(function(tx) { | |||
if (tx) { | |||
res.redirect("/tx/" + query); | |||
return; | |||
} | |||
}).catch(function(err) { | |||
rpcApi.getBlockByHash(query).then(function(blockByHash) { | |||
if (blockByHash) { | |||
res.redirect("/block/" + query); | |||
return; | |||
} | |||
}).catch(function(err) { | |||
if (isNaN(query)) { | |||
req.session.userMessage = "No results found for query: " + query; | |||
res.redirect("/"); | |||
return; | |||
} | |||
rpcApi.getBlockByHeight(parseInt(query)).then(function(blockByHeight) { | |||
if (blockByHeight) { | |||
res.redirect("/block-height/" + query); | |||
return; | |||
} | |||
}).catch(function(err) { | |||
req.session.userMessage = "No results found for query: " + query; | |||
res.redirect("/"); | |||
}); | |||
}); | |||
}); | |||
}); | |||
router.get("/block-height/:blockHeight", function(req, res) { | |||
var client = global.client; | |||
var blockHeight = parseInt(req.params.blockHeight); | |||
res.locals.blockHeight = blockHeight; | |||
res.locals.result = {}; | |||
var limit = 20; | |||
var offset = 0; | |||
if (req.query.limit) { | |||
limit = parseInt(req.query.limit); | |||
} | |||
if (req.query.offset) { | |||
offset = parseInt(req.query.offset); | |||
} | |||
res.locals.limit = limit; | |||
res.locals.offset = offset; | |||
res.locals.paginationBaseUrl = "/block-height/" + blockHeight; | |||
client.cmd('getblockhash', blockHeight, function(err, result, resHeaders) { | |||
if (err) { | |||
// TODO handle RPC error | |||
return console.log(err); | |||
} | |||
res.locals.result.getblockhash = result; | |||
rpcApi.getBlockData(client, result, limit, offset).then(function(result) { | |||
res.locals.result.getblock = result.getblock; | |||
res.locals.result.transactions = result.transactions; | |||
res.locals.result.txInputsByTransaction = result.txInputsByTransaction; | |||
res.render("block-height"); | |||
}); | |||
}); | |||
}); | |||
router.get("/block/:blockHash", function(req, res) { | |||
var blockHash = req.params.blockHash; | |||
res.locals.blockHash = blockHash; | |||
res.locals.result = {}; | |||
var limit = 20; | |||
var offset = 0; | |||
if (req.query.limit) { | |||
limit = parseInt(req.query.limit); | |||
} | |||
if (req.query.offset) { | |||
offset = parseInt(req.query.offset); | |||
} | |||
res.locals.limit = limit; | |||
res.locals.offset = offset; | |||
res.locals.paginationBaseUrl = "/block/" + blockHash; | |||
// TODO handle RPC error | |||
rpcApi.getBlockData(client, blockHash, limit, offset).then(function(result) { | |||
res.locals.result.getblock = result.getblock; | |||
res.locals.result.transactions = result.transactions; | |||
res.locals.result.txInputsByTransaction = result.txInputsByTransaction; | |||
res.render("block"); | |||
}); | |||
}); | |||
router.get("/tx/:transactionId", function(req, res) { | |||
var txid = req.params.transactionId; | |||
var output = -1; | |||
if (req.query.output) { | |||
output = parseInt(req.query.output); | |||
} | |||
res.locals.txid = txid; | |||
res.locals.output = output; | |||
res.locals.result = {}; | |||
// TODO handle RPC error | |||
rpcApi.getRawTransaction(txid).then(function(rawTxResult) { | |||
res.locals.result.getrawtransaction = rawTxResult; | |||
client.cmd('getblock', rawTxResult.blockhash, function(err3, result3, resHeaders3) { | |||
res.locals.result.getblock = result3; | |||
var txids = []; | |||
for (var i = 0; i < rawTxResult.vin.length; i++) { | |||
if (!rawTxResult.vin[i].coinbase) { | |||
txids.push(rawTxResult.vin[i].txid); | |||
} | |||
} | |||
rpcApi.getRawTransactions(txids).then(function(txInputs) { | |||
res.locals.result.txInputs = txInputs; | |||
res.render("transaction"); | |||
}); | |||
}); | |||
}); | |||
}); | |||
router.get("/terminal", function(req, res) { | |||
if (!env.debug) { | |||
res.send("Debug mode is off."); | |||
return; | |||
} | |||
res.render("terminal"); | |||
}); | |||
router.post("/terminal", function(req, res) { | |||
if (!env.debug) { | |||
res.send("Debug mode is off."); | |||
return; | |||
} | |||
client.cmd(req.body.cmd, function(err, result, resHeaders) { | |||
console.log(result); | |||
console.log(err); | |||
console.log(resHeaders); | |||
res.send(JSON.stringify(result, null, 4)); | |||
}); | |||
}); | |||
module.exports = router; |
@@ -0,0 +1,10 @@ | |||
extends layout | |||
block headContent | |||
title Block #{blockHeight} | |||
block content | |||
h1(class="h2") Block #{blockHeight} | |||
hr | |||
include includes/block-content.pug |
@@ -0,0 +1,12 @@ | |||
extends layout | |||
block headContent | |||
title Block #{blockHash} | |||
block content | |||
h1(class="h2") Block | |||
br | |||
small(class="monospace") #{result.getblock.hash} | |||
hr | |||
include includes/block-content.pug |
@@ -0,0 +1,35 @@ | |||
extends layout | |||
block content | |||
h1(class="h2") Blocks | |||
hr | |||
if (blocks) | |||
nav(aria-label="Page navigation") | |||
ul(class="pagination justify-content-center") | |||
li(class="page-item", class=(sort == "asc" ? "active" : false)) | |||
a(class="page-link", href=(sort == "asc" ? "javascript:void(0)" : "/blocks?limit=" + limit + "&offset=0" + "&sort=asc")) | |||
span(aria-hidden="true") Oldest blocks first | |||
li(class="page-item", class=(sort == "desc" ? "active" : false)) | |||
a(class="page-link", href=(sort == "desc" ? "javascript:void(0)" : "/blocks?limit=" + limit + "&offset=0" + "&sort=desc")) | |||
span(aria-hidden="true") Newest blocks first | |||
include includes/blocks-list.pug | |||
hr | |||
if (blockCount > limit) | |||
- var pageNumber = offset / limit + 1; | |||
- var pageCount = Math.floor(blockCount / limit); | |||
- if (pageCount * limit < blockCount) { | |||
- pageCount++; | |||
- } | |||
- var paginationUrlFunction = function(x) { | |||
- return paginationBaseUrl + "?limit=" + limit + "&offset=" + ((x - 1) * limit + "&sort=" + sort); | |||
- } | |||
hr | |||
include includes/pagination.pug | |||
else | |||
p No blocks found |
@@ -0,0 +1 @@ | |||
td(class="monospace") 0000002 |
@@ -0,0 +1,37 @@ | |||
extends layout | |||
block content | |||
h1 Vivocoin RPC Explorer | |||
hr | |||
:markdown-it | |||
This tool is intended to be a simple, stateless, self-hosted explorer for the Vivocoin blockchain, driven by RPC calls to your own vivod node. Because it is stateless, it is easy to run but lacks some (many?) of the features of other explorers. | |||
Start by connecting to your full, archiving smartcashd node. Make sure that the node you'll be connecting to has `txindex=1` set. | |||
form(method="post", action="/connect") | |||
div(class="card") | |||
div(class="card-body") | |||
h4(class="card-title") RPC Connect | |||
hr | |||
div(class="form-group") | |||
label(for="input-host") Host / IP | |||
input(type="text", name="host", class="form-control", value=host) | |||
div(class="form-group") | |||
label(for="input-host") Port | |||
input(type="text", name="port", class="form-control", value=port) | |||
div(class="form-group") | |||
label(for="input-host") Username | |||
input(type="text", name="username", class="form-control", value=username) | |||
div(class="form-group") | |||
label(for="input-host") Password | |||
input(type="password", name="password", class="form-control") | |||
hr | |||
input(type="submit", class="btn btn-primary btn-block" value="Connect") |
@@ -0,0 +1,14 @@ | |||
extends layout | |||
block content | |||
h1 Error | |||
hr | |||
if (message) | |||
p !{message} | |||
else | |||
p Unknown error | |||
if (error) | |||
h2 #{error.status} | |||
pre #{error.stack} |
@@ -0,0 +1,245 @@ | |||
ul(class='nav nav-tabs mb-3') | |||
li(class="nav-item") | |||
a(data-toggle="tab", href="#tab-summary", class="nav-link active", role="tab") Summary | |||
li(class="nav-item") | |||
a(data-toggle="tab", href="#tab-raw", class="nav-link", role="tab") Raw | |||
- var txCount = result.getblock.tx.length; | |||
div(class="tab-content") | |||
div(id="tab-summary", class="tab-pane active", role="tabpanel") | |||
table(class="table") | |||
tr | |||
th(class="table-active properties-header") Block Hash | |||
td(class="monospace") | |||
a(href=("/block/" + result.getblock.hash)) #{result.getblock.hash} | |||
tr | |||
th(class="table-active properties-header") Previous Block Hash | |||
td(class="monospace") | |||
if (result.getblock.previousblockhash) | |||
a(href=("/block/" + result.getblock.previousblockhash)) #{result.getblock.previousblockhash} | |||
tr | |||
th(class="table-active properties-header") Next Block Hash | |||
td(class="monospace") | |||
if (result.getblock.nextblockhash) | |||
a(href=("/block/" + result.getblock.nextblockhash)) #{result.getblock.nextblockhash} | |||
else | |||
span None | |||
span(class="text-muted") (latest block) | |||
tr | |||
th(class="table-active properties-header") Block Height | |||
td(class="monospace") | |||
a(href=("/block-height/" + result.getblock.height)) #{result.getblock.height} | |||
tr | |||
th(class="table-active properties-header") Timestamp | |||
td(class="monospace") #{moment.utc(new Date(result.getblock.time * 1000)).format("Y-MM-DD HH:mm:ss")} (utc) | |||
tr | |||
th(class="table-active properties-header") Transaction Count | |||
td(class="monospace") #{result.getblock.tx.length.toLocaleString()} | |||
tr | |||
th(class="table-active properties-header") Size | |||
td(class="monospace") | |||
span #{result.getblock.size.toLocaleString()} bytes | |||
if (result.getblock.weight) | |||
br | |||
span(class="text-muted") (weight: #{result.getblock.weight.toLocaleString()}) | |||
tr | |||
th(class="table-active properties-header") Confirmations | |||
td(class="monospace") | |||
if (result.getblock.confirmations < 6) | |||
strong(class="text-warning") #{result.getblock.confirmations} | |||
else | |||
strong(class="text-success") #{result.getblock.confirmations.toLocaleString()} | |||
tr | |||
- var scales = [ {val:1000000000000000, name:"quadrillion"}, {val:1000000000000, name:"trillion"}, {val:1000000000, name:"billion"}, {val:1000000, name:"million"} ]; | |||
- var scaleDone = false; | |||
th(class="table-active properties-header") Difficulty | |||
td(class="monospace") | |||
span #{result.getblock.difficulty.toLocaleString()} | |||
each item in scales | |||
if (!scaleDone) | |||
- var fraction = Math.floor(result.getblock.difficulty / item.val); | |||
if (fraction >= 1) | |||
- scaleDone = true; | |||
span(class="text-muted") (#{fraction} #{item.name}) | |||
tr | |||
th(class="table-active text-right") Version | |||
td(class="monospace") 0x#{result.getblock.versionHex} | |||
span(class="text-muted") (decimal: #{result.getblock.version}) | |||
tr | |||
th(class="table-active text-right") Nonce | |||
td(class="monospace") #{result.getblock.nonce} | |||
tr | |||
th(class="table-active text-right") Bits | |||
td(class="monospace") #{result.getblock.bits} | |||
tr | |||
th(class="table-active text-right") Merkle Root | |||
td(class="monospace") #{result.getblock.merkleroot} | |||
tr | |||
th(class="table-active text-right") Chainwork | |||
td(class="monospace") #{result.getblock.chainwork} | |||
hr | |||
h2(class="h4") Transactions (#{txCount.toLocaleString()}) | |||
small - Showing | |||
if (txCount <= limit) | |||
span all | |||
else | |||
span #{(offset + 1)} - #{Math.min(offset + limit, txCount)} | |||
each tx, txIndex in result.transactions | |||
//pre | |||
// code #{JSON.stringify(tx, null, 4)} | |||
div(class="card mb-3") | |||
div(class="card-header") | |||
if (tx && tx.txid) | |||
a(href=("/tx/" + tx.txid), class="monospace") #{tx.txid} | |||
else if (result.getblock.height == 0) | |||
a(href=(""), class="monospace") #{result.getblock.tx} | |||
div(class="card-body") | |||
//pre | |||
// code #{JSON.stringify(result.txInputsByTransaction[tx.txid], null, 4)} | |||
if (tx && tx.txid) | |||
div(class="row") | |||
div(class="col-md-6") | |||
h6 Input (#{tx.vin.length.toLocaleString()}) | |||
if (result.txInputsByTransaction[tx.txid]) | |||
- var totalInputValue = new Decimal(0); | |||
table(class="table mb-0") | |||
thead | |||
tr | |||
th(style="width: 40px;") | |||
th Input | |||
th Amount | |||
tbody | |||
if (tx.vin[0].coinbase) | |||
- totalInputValue = totalInputValue.plus(new Decimal(utils.getBlockReward(result.getblock.height))); | |||
tr | |||
th 1 | |||
td | |||
span(class="tag monospace") coinbase | |||
span(class="monospace") Newly minted VIVO | |||
td(class="monospace") Σ #{utils.getBlockReward(result.getblock.height)} | |||
each txInput, txInputIndex in result.txInputsByTransaction[tx.txid] | |||
if (txInput) | |||
- var vout = txInput.vout[tx.vin[txInputIndex].vout]; | |||
tr | |||
th #{(txInputIndex + 1)} | |||
//pre | |||
// code #{JSON.stringify(txInput)} | |||
td | |||
if (vout.scriptPubKey && vout.scriptPubKey.addresses) | |||
span(class="monospace") #{vout.scriptPubKey.addresses[0]} | |||
br | |||
span(class="monospace text-muted") via tx | |||
a(href=("/tx/" + txInput.txid + "#output-" + tx.vin[txInputIndex].vout), class="monospace") #{txInput.txid.substring(0, 14)}..., Output ##{tx.vin[txInputIndex].vout + 1} | |||
td | |||
if (vout.value) | |||
- totalInputValue = totalInputValue.plus(new Decimal(vout.value)); | |||
span(class="monospace") Σ #{vout.value} | |||
- var coinbaseCount = tx.vin[0].coinbase ? 1 : 0; | |||
if ((tx.vin.length - coinbaseCount) > result.txInputsByTransaction[tx.txid].length) | |||
tr | |||
td | |||
td | |||
//span(class="monospace text-muted") #{(tx.vin.length - result.txInputsByTransaction[tx.txid].length).toLocaleString()} : click on transaction id for all ... | |||
//span(class="monospace text-muted") a(href=("/tx/" + tx.txid), click on transaction id for all ....)} | |||
//a(href=""/tx/" + #{tx.txid}", class="nav-link") more ... #{tx.txid} | |||
//a(href=("/tx/" + tx.txid), class="monospace") #{tx.txid} | |||
span(class="monospace text-muted") #{(tx.vin.length - result.txInputsByTransaction[tx.txid].length).toLocaleString()}: | |||
//a(href=("/tx/" + tx.txid), class="monospace") | |||
a(href=("/tx/" + tx.txid), class="monospace") more .. | |||
td | |||
else | |||
tr | |||
td | |||
td | |||
td | |||
strong(class="monospace") Σ #{totalInputValue} | |||
div(class="col-md-6") | |||
h6 Output (#{tx.vout.length.toLocaleString()}) | |||
- var totalOutputValue = new Decimal(0); | |||
table(class="table mb-0") | |||
thead | |||
tr | |||
th | |||
th Output | |||
th Amount | |||
tbody | |||
each vout, voutIndex in tx.vout | |||
tr | |||
th #{(voutIndex + 1)} | |||
td | |||
if (vout.scriptPubKey) | |||
if (vout.scriptPubKey.addresses) | |||
a(id="output-" + voutIndex) | |||
span(class="monospace") #{vout.scriptPubKey.addresses[0]} | |||
else if (vout.scriptPubKey.hex && vout.scriptPubKey.hex.startsWith('6a24aa21a9ed')) | |||
span(class="monospace") Segregated Witness committment - | |||
a(href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#commitment-structure") docs | |||
i(class="fa fa-external-link") | |||
else if (vout.scriptPubKey.asm && vout.scriptPubKey.asm.startsWith('OP_RETURN ')) | |||
span(class="monospace") OP_RETURN: | |||
span(class="monospace text-muted") #{utils.hex2ascii(vout.scriptPubKey.asm.substring("OP_RETURN ".length))} | |||
td | |||
span(class="monospace") Σ #{vout.value} | |||
- totalOutputValue = totalOutputValue.plus(vout.value); | |||
tr | |||
td | |||
td | |||
td | |||
strong(class="monospace") Σ #{totalOutputValue} | |||
else if (result.getblock.height == 0) | |||
table(class="table mb-0") | |||
thead | |||
tr | |||
th(style="text-align: center;") Genesis | |||
//pre | |||
// code #{JSON.stringify(tx, null, 4)} | |||
if (txCount > limit) | |||
- var pageNumber = offset / limit + 1; | |||
- var pageCount = Math.floor(txCount / limit); | |||
- if (pageCount * limit < txCount) { | |||
- pageCount++; | |||
- } | |||
- var paginationUrlFunction = function(x) { | |||
- return paginationBaseUrl + "?limit=" + limit + "&offset=" + ((x - 1) * limit); | |||
- } | |||
hr | |||
include ./pagination.pug | |||
div(id="tab-raw", class="tab-pane", role="tabpanel") | |||
pre | |||
code #{JSON.stringify(result.getblock, null, 4)} |
@@ -0,0 +1,22 @@ | |||
table(class="table table-striped") | |||
thead | |||
tr | |||
th | |||
th(class="data-header") Height | |||
th(class="data-header") Timestamp (utc) | |||
th(class="data-header") Age (seconds) | |||
th(class="data-header") Transactions | |||
//th(class="data-header") Size (bytes) | |||
tbody | |||
each block, blockIndex in blocks | |||
if (block) | |||
tr | |||
th #{(blockIndex + blockOffset + 1).toLocaleString()} | |||
td(class="data-cell monospace") | |||
a(href=("/block-height/" + block.height)) #{block.height} | |||
td(class="data-cell monospace") #{moment.utc(new Date(parseInt(block.time) * 1000)).format("Y-MM-DD HH:mm:ss")} | |||
- var timeAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(block.time) * 1000)))); | |||
td(class="data-cell monospace") #{timeAgo.format()} | |||
td(class="data-cell monospace") #{block.tx.length.toLocaleString()} | |||
//td(class="data-cell monospace") #{block.size.toLocaleString()} |
@@ -0,0 +1,26 @@ | |||
- var pageNumbers = []; | |||
- for (var x = 1; x <= pageCount; x++) { | |||
- pageNumbers.push(x); | |||
- } | |||
nav(aria-label="Page navigation") | |||
ul(class="pagination pagination-lg justify-content-center") | |||
li(class="page-item", class=(pageNumber == 1 ? "disabled" : false)) | |||
a(class="page-link", href=(pageNumber == 1 ? "javascript:void(0)" : paginationUrlFunction(pageNumber - 1)), aria-label="Previous") | |||
span(aria-hidden="true") « | |||
each x, xIndex in pageNumbers | |||
if (x >= (pageNumber - 4) && x <= (pageNumber + 4) || xIndex == 0 || xIndex == (pageNumbers.length - 1)) | |||
li(class="page-item", class=(x == pageNumber ? "active" : false)) | |||
a(class="page-link", href=(paginationUrlFunction(x))) #{x} | |||
if (x == 1 && pageNumber > 6) | |||
li(class="page-item disabled") | |||
a(class="page-link", href="javascript:void(0)") ... | |||
else if (x == (pageCount - 1) && pageNumber < (pageCount - 5)) | |||
li(class="page-item disabled") | |||
a(class="page-link", href="javascript:void(0)") ... | |||
li(class="page-item", class=(pageNumber == pageCount ? "disabled" : false)) | |||
a(class="page-link", href=(pageNumber == pageCount ? "javascript:void(0)" : paginationUrlFunction(pageNumber + 1)), aria-label="Next") | |||
span(aria-hidden="true") » |
@@ -0,0 +1,31 @@ | |||
extends layout | |||
block headContent | |||
title Home | |||
block content | |||
h1 Vivocoin Quick Explorer | |||
hr | |||
:markdown-it | |||
**Vivocoin Explorer 3** is an explorer with fast setup time. The Larger Explorers are at [http://explorer7.vivocoin.net](http://explorer7.vivocoin.net) and [http://explorer6.vivocoin.net](http://explorer6.vivocoin.net) | |||
if (latestBlocks) | |||
h3 Latest Blocks | |||
- var blocks = latestBlocks; | |||
- var blockOffset = 0; | |||
include includes/blocks-list.pug | |||
hr | |||
a(href="/blocks", class="btn btn-primary btn-block") See more | |||
hr | |||
div(class="text-center") | |||
:markdown-it | |||
More information available on Website: [http://vivocoin.net](http://vivocoin.net) | |||
[© 2021 Vivocoin](http://vivocoin.net/) |
@@ -0,0 +1,74 @@ | |||
doctype html | |||
html | |||
head | |||
meta(charset="utf-8") | |||
meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no") | |||
link(rel="stylesheet", href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css", integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb", crossorigin="anonymous") | |||
link(rel="stylesheet", href="https://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css") | |||
link(rel="stylesheet", href="https://fonts.googleapis.com/css?family=Lato|Open+Sans|Cousine") | |||
link(rel="stylesheet", href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css") | |||
link(rel='stylesheet', href='/css/styling.css') | |||
link(rel="icon", type="image/png", sizes="32x32", href="/img/favicons/favicon-32x32.png") | |||
link(rel="icon", type="image/png", sizes="96x96", href="/img/favicons/favicon-96x96.png") | |||
link(rel="icon", type="image/png", sizes="16x16", href="/img/favicons/favicon-16x16.png") | |||
block headContent | |||
title Vivocoin Explorer | |||
body | |||
nav(class="navbar navbar-expand-lg navbar-light bg-dark mb-4") | |||
div(class="container") | |||
a(class="navbar-brand", href="/") | |||
span | |||
img(src="/img/logo/logo-64.png", class="header-image") | |||
span Vivocoin Explorer | |||
button(type="button", class="navbar-toggler navbar-toggler-right", data-toggle="collapse", data-target="#navbarNav") | |||
span(class="navbar-toggler-icon") | |||
div(class="collapse navbar-collapse", id="navbarNav") | |||
if (client) | |||
ul(class="navbar-nav mr-auto") | |||
if (debug) | |||
li(class="nav-item") | |||
a(href="/terminal", class="nav-link") RPC Terminal | |||
li(class="nav-item") | |||
a(href="/node-info", class="nav-link") Node Info | |||
li(class="nav-item") | |||
a(href="/mempool", class="nav-link") Mempool Info | |||
form(method="post", action="/search", class="form-inline") | |||
div(class="input-group") | |||
input(type="text", class="form-control form-control-sm", name="query", placeholder="block height, block hash, txid", style="width: 250px;") | |||
span(class="input-group-btn") | |||
input(type="submit", class="btn btn-primary", value="Search") | |||
div(class="container") | |||
if (userMessage) | |||
div(class="alert", class=(userMessageType ? ("alert-" + userMessageType) : "alert-info"), role="alert") | |||
span !{userMessage} | |||
block content | |||
div(style="margin-bottom: 30px;") | |||
script(src="https://code.jquery.com/jquery-3.2.1.min.js", integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=", crossorigin="anonymous") | |||
script(src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js", integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh", crossorigin="anonymous") | |||
script(src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js", integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb", crossorigin="anonymous") | |||
script(src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js", integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1", crossorigin="anonymous") | |||
script(src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js") | |||
script(src="https://cdn.ravenjs.com/3.9.1/raven.min.js") | |||
script. | |||
Raven.config('https://0bf20e8357a748cab8aa9d35c0f790dd@sentry.io/130800').install(); | |||
$(document).ready(function() { | |||
$('[data-toggle="tooltip"]').tooltip(); | |||
$('[data-toggle="popover"]').popover({html:true}); | |||
}); | |||
hljs.initHighlightingOnLoad(); | |||
block endOfBody |
@@ -0,0 +1,75 @@ | |||
extends layout | |||
block headContent | |||
title Mempool Info | |||
block content | |||
h1 Mempool Info | |||
hr | |||
if (getmempoolinfo) | |||
p Data from RPC command | |||
a(href="https://bitcoin.org/en/developer-reference#getmempoolinfo") getmempoolinfo | |||
table(class="table") | |||
tr | |||
th(class="table-active properties-header") Transaction Count | |||
td #{getmempoolinfo.size.toLocaleString()} | |||
tr | |||
- var scales = [ {val:1000000000, name:"GB"}, {val:1000000, name:"MB"}, {val:1000, name:"kB"} ]; | |||
- var scaleDone = false; | |||
th(class="table-active properties-header") Tx Size | |||
td(class="monospace") | |||
span #{getmempoolinfo.bytes.toLocaleString()} bytes | |||
each item in scales | |||
if (!scaleDone) | |||
- var fraction = Math.floor(getmempoolinfo.bytes / item.val); | |||
if (fraction >= 1) | |||
- scaleDone = true; | |||
span(class="text-muted") (#{fraction} #{item.name}) | |||
tr | |||
- var scales = [ {val:1000000000, name:"GB"}, {val:1000000, name:"MB"}, {val:1000, name:"kB"} ]; | |||
- var scaleDone = false; | |||
th(class="table-active properties-header") Total Size | |||
td(class="monospace") | |||
span #{getmempoolinfo.usage.toLocaleString()} bytes | |||
each item in scales | |||
if (!scaleDone) | |||
- var fraction = Math.floor(getmempoolinfo.usage / item.val); | |||
if (fraction >= 1) | |||
- scaleDone = true; | |||
span(class="text-muted") (#{fraction} #{item.name}) | |||
tr | |||
th(class="table-active properties-header") Max Size | |||
td(class="monospace") #{getmempoolinfo.maxmempool.toLocaleString()} | |||
tr | |||
th(class="table-active properties-header") Min Fee | |||
td(class="monospace") #{getmempoolinfo.mempoolminfee.toLocaleString()} | |||
tr | |||
th(class="table-active properties-header") Total Fees | |||
td(class="monospace") #{mempoolstats.totalFee.toLocaleString()} | |||
h4 Transaction count by fee level | |||
hr | |||
if (false) | |||
#{JSON.stringify(mempoolstats)} | |||
- var feeBucketLabels = [ "0 - 5 sat/B", "6 - 10 sat/B", "11 - 25 sat/B", "26 - 50 sat/B", "51 - 75 sat/B", "76 - 100 sat/B", "101 - 150 sat/B", "151+ sat/B" ]; | |||
- var feeBucketTxCounts = [ mempoolstats.fee_0_5, mempoolstats.fee_6_10, mempoolstats.fee_11_25, mempoolstats.fee_26_50, mempoolstats.fee_51_75, mempoolstats.fee_76_100, mempoolstats.fee_101_150, mempoolstats.fee_151_max ]; | |||
- var bgColors = [ "bg-primary", "bg-success", "bg-info", "bg-warning", "bg-danger", "bg-primary progress-bar-striped", "bg-success progress-bar-striped", "bg-info progress-bar-striped" ]; | |||
table(class="table") | |||
each feeBucketLabel, index in feeBucketLabels | |||
tr | |||
th(class=("properties-header " + bgColors[index])) #{feeBucketLabel} | |||
td(class="monospace") #{feeBucketTxCounts[index].toLocaleString()} | |||
div(class="progress") | |||
each txCount, index in feeBucketTxCounts | |||
- var percent = 100 * txCount / getmempoolinfo.size; | |||
div(class=("progress-bar " + bgColors[index]), role="progressbar", style=("width: " + percent + "%;"), aria-valuenow=percent, aria-valuemin="0", aria-valuemax="100") | |||
span #{txCount} | |||
@@ -0,0 +1,75 @@ | |||
extends layout | |||
block headContent | |||
title Node Info | |||
block content | |||
h1 Node Info | |||
hr | |||
if (getinfo) | |||
//p Data from RPC command | |||
// a(href="https://bitcoin.org/en/developer-reference#getinfo") getinfo | |||
if (false) | |||
pre | |||
code #{JSON.stringify(getinfo, null, 4)} | |||
if (true) | |||
table(class="table") | |||
tr | |||
th(class="table-active properties-header") BTC Price | |||
include btcprice | |||
tr | |||
th(class="table-active properties-header") Hash amount | |||
td(class="monospace") #{getmininginfo.networkhashps.toLocaleString()} | |||
tr | |||
th(class="table-active properties-header") Block Count | |||
td(class="monospace") #{getinfo.blocks} | |||
tr | |||
th(class="table-active properties-header") Version | |||
td(class="monospace") #{getinfo.version} | |||
tr | |||
th(class="table-active properties-header") Protocol Version | |||
td(class="monospace") #{getinfo.protocolversion} | |||
tr | |||
th(class="table-active properties-header") Connections | |||
td(class="monospace") #{getinfo.connections.toLocaleString()} | |||
tr | |||
th(class="table-active properties-header") Testnet? | |||
td(class="monospace") #{getinfo.testnet} | |||
tr | |||
th(class="table-active properties-header") Errors | |||
td(class="monospace") #{getinfo.errors} | |||
//tr | |||
// th(class="table-active properties-header") Hash Amount | |||
// td(class="monospace") #{getmininginfo.networkhashps} | |||
tr | |||
- var scales = [ {val:1000000000000000, name:"quadrillion"}, {val:1000000000000, name:"trillion"}, {val:1000000000, name:"billion"}, {val:1000000, name:"million"} ]; | |||
- var scaleDone = false; | |||
th(class="table-active properties-header") Difficulty | |||
td(class="monospace") | |||
span #{getinfo.difficulty.toLocaleString()} | |||
each item in scales | |||
if (!scaleDone) | |||
- var fraction = Math.floor(getinfo.difficulty / item.val); | |||
if (fraction >= 1) | |||
- scaleDone = true; | |||
span(class="text-muted") (#{fraction} #{item.name}) | |||
//tr | |||
// th(class="table-active properties-header") Nodes | |||
// td | |||
// a(href="https://smartcash.bitcoiner.me/smartnodes/worldmap/") | |||
// img(src="https://smartcash.bitcoiner.me/smartnodes/worldmap/map.php") | |||
//if (getmininginfo) | |||
// table(class="table") | |||
// tr | |||
// th(class="table-active properties-header") Hash Amount | |||
// td(class="monospace") #{getmininginfo.networkhashps} | |||
tr | |||
th(class="table-active properties-header") Version | |||
td(class="monospace") #{getinfo.version} |
@@ -0,0 +1,54 @@ | |||
extends layout | |||
block content | |||
h1 Terminal | |||
hr | |||
:markdown-it | |||
Use this interactive terminal to send RPC commands to your node. Results will be shown inline. | |||
div(class="card mb-3") | |||
div(class="card-body") | |||
form(id="terminal-form") | |||
div(class="form-group") | |||
label(for="input-cmd") Command | |||
input(type="text", id="input-cmd", name="cmd", class="form-control") | |||
input(type="submit", class="btn btn-primary btn-block", value="Send") | |||
hr | |||
div(id="terminal-output") | |||
block endOfBody | |||
script. | |||
$(document).ready(function() { | |||
$("#terminal-form").submit(function(e) { | |||
e.preventDefault(); | |||
var cmd = $("#input-cmd").val() | |||
var postData = {}; | |||
postData.cmd = cmd; | |||
$.post( | |||
"/terminal", | |||
postData, | |||
function(response, textStatus, jqXHR) { | |||
var t = new Date().getTime(); | |||
$("#terminal-output").prepend("<div id='output-" + t + "' class='card mb-3'><div class='card-body'><h5>" + cmd + "</h5><pre><code>" + response + "</code></pre></div></div>"); | |||
console.log(response); | |||
$("#output-" + t + " pre code").each(function(i, block) { | |||
hljs.highlightBlock(block); | |||
}); | |||
return false; | |||
}) | |||
.done(function(data) { | |||
}); | |||
return false; | |||
}); | |||
}); |
@@ -0,0 +1,267 @@ | |||
extends layout | |||
block headContent | |||
title Transaction #{txid} | |||
style. | |||
.field { | |||
word-wrap: break-word; | |||
} | |||
block content | |||
h1(class="h2") Transaction | |||
br | |||
small(class="monospace") #{txid} | |||
hr | |||
ul(class='nav nav-tabs mb-3') | |||
li(class="nav-item") | |||
a(data-toggle="tab", href="#tab-summary", class="nav-link active", role="tab") Summary | |||
li(class="nav-item") | |||
a(data-toggle="tab", href="#tab-scripts", class="nav-link", role="tab") Scripts | |||
li(class="nav-item") | |||
a(data-toggle="tab", href="#tab-raw", class="nav-link", role="tab") Raw | |||
- DecimalRounded = Decimal.clone({ precision: 4, rounding: 2 }) | |||
- var totalInputValue = new Decimal(0); | |||
if (result.getrawtransaction.vin[0].coinbase) | |||
- totalInputValue = totalInputValue.plus(new Decimal(utils.getBlockReward(result.getblock.height))); | |||
each txInput, txInputIndex in result.txInputs | |||
if (txInput) | |||
- var vout = txInput.vout[result.getrawtransaction.vin[txInputIndex].vout]; | |||
if (vout.value) | |||
- totalInputValue = totalInputValue.plus(new Decimal(vout.value)); | |||
- var totalOutputValue = new Decimal(0); | |||
each vout, voutIndex in result.getrawtransaction.vout | |||
- totalOutputValue = totalOutputValue.plus(new Decimal(vout.value)); | |||
div(class="tab-content") | |||
div(id="tab-summary", class="tab-pane active", role="tabpanel") | |||
if (txid == "00000f6be3e151f9082a2b82c2916192a791090015b80979934a45d625460d62") | |||
div(class="alert alert-warning", style="padding-bottom: 0;") | |||
h4(class="alert-heading h5") This transaction doesn't really exist! | |||
:markdown-it | |||
This is the coinbase transaction of the [Vivocoin Genesis Block](/block/00000f6be3e151f9082a2b82c2916192a791090015b80979934a45d625460d62). For more background about this special-case transaction, you can read [this brief discussion](https://github.com/bitcoin/bitcoin/issues/3303) among some of the [Vivocoin Core](https://bitcoin.org) developers. | |||
table(class="table") | |||
tr | |||
th(class="table-active properties-header") Included in Block | |||
td(class="monospace") | |||
if (result.getblock) | |||
a(href=("/block/" + result.getrawtransaction.blockhash)) #{result.getrawtransaction.blockhash} | |||
if (result.getblock.height) | |||
span(class="text-muted") (#{result.getblock.height.toLocaleString()}) | |||
else | |||
span N/A | |||
span(class="text-muted") (unconfirmed) | |||
tr | |||
th(class="table-active properties-header") Timestamp | |||
if (result.getrawtransaction.time) | |||
td(class="monospace") #{moment.utc(new Date(result.getrawtransaction["time"] * 1000)).format("Y-MM-DD HH:mm:ss")} (utc) | |||
else | |||
td(class="monospace") N/A | |||
span(class="text-muted") (unconfirmed) | |||
//tr | |||
// th(class="table-active properties-header") Transaction ID | |||
// td #{txid} | |||
tr | |||
th(class="table-active properties-header") Version | |||
td(class="monospace") #{result.getrawtransaction.version} | |||
if (result.getrawtransaction.locktime > 0) | |||
tr | |||
th(class="table-active properties-header") | |||
span Locktime | |||
td(class="monospace") | |||
if (result.getrawtransaction.locktime < 500000000) | |||
span Spendable in block | |||
a(href=("/block-height/" + result.getrawtransaction.locktime)) #{result.getrawtransaction.locktime} | |||
span or later - ( | |||
a(href="https://bitcoin.org/en/developer-guide#locktime-and-sequence-number", title="Locktime documentation") | |||
span docs | |||
i(class="fa fa-external-link") | |||
span ) | |||
else | |||
span Spendable after #{moment.utc(new Date(result.getrawtransaction.locktime * 1000)).format("Y-MM-DD HH:mm:ss")} (utc) - ( | |||
a(href="https://bitcoin.org/en/developer-guide#locktime-and-sequence-number", title="Locktime documentation") | |||
span docs | |||
i(class="fa fa-external-link") | |||
span ) | |||
tr | |||
th(class="table-active properties-header") Confirmations | |||
td(class="monospace") | |||
if (!result.getrawtransaction.confirmations || result.getrawtransaction.confirmations == 0) | |||
strong(class="text-danger") 0 (unconfirmed) | |||
else if (result.getrawtransaction.confirmations < 6) | |||
strong(class="text-warning") #{result.getrawtransaction.confirmations} | |||
else | |||
strong(class="text-success") #{result.getrawtransaction.confirmations.toLocaleString()} | |||
if (result.getrawtransaction.vin[0].coinbase) | |||
tr | |||
th(class="table-active properties-header") Total Network Fees | |||
//td(class="monospace") Σ #{new Decimal(totalOutputValue).minus(totalInputValue)} | |||
td(class="monospace") Σ #{(totalOutputValue - totalInputValue)} | |||
else | |||
tr | |||
th(class="table-active properties-header") Network Fee Paid | |||
td(class="monospace") | |||
strong #{new Decimal(totalInputValue).minus(totalOutputValue)} | |||
//span(class="text-muted") (#{totalInputValue} - #{totalOutputValue}) | |||
//br | |||
//span ~#{new DecimalRounded(totalInputValue).minus(totalOutputValue).dividedBy(result.getrawtransaction.size).times(100000000)} sat/B | |||
if (result.getrawtransaction.vin[0].coinbase) | |||
div(class="card mb-3") | |||
div(class="card-header") | |||
h2(class="h5 mb-0") Coinbase | |||
div(class="card-body") | |||
h6 Hex | |||
div(style="background-color: #fafafa; padding: 5px 10px;", class="mb-3") | |||
span(class="monospace word-wrap") #{result.getrawtransaction.vin[0].coinbase} | |||
h6 Decoded | |||
div(style="background-color: #fafafa; padding: 5px 10px;", class="mb-3") | |||
span(class="monospace word-wrap") #{utils.hex2ascii(result.getrawtransaction.vin[0].coinbase)} | |||
div(class="card mb-3") | |||
div(class="card-header") | |||
div(class="row") | |||
div(class="col-md-6") | |||
h2(class="h5 mb-0") Input (#{result.getrawtransaction.vin.length.toLocaleString()}) | |||
div(class="col-md-6") | |||
h2(class="h5 mb-0") Output (#{result.getrawtransaction.vout.length.toLocaleString()}) | |||
div(class="card-body") | |||
div(class="row") | |||
div(class="col-md-6") | |||
if (result.txInputs) | |||
table(class="table mb-0") | |||
thead | |||
tr | |||
th(style="width: 40px;") | |||
th Input | |||
th Amount | |||
tbody | |||
if (result.getrawtransaction.vin[0].coinbase) | |||
tr | |||
th 1 | |||
td | |||
span(class="tag monospace") coinbase | |||
span(class="monospace") Newly minted VIVO | |||
td(class="monospace") Σ #{utils.getBlockReward(result.getblock.height)} | |||
each txInput, txInputIndex in result.txInputs | |||
if (txInput) | |||
- var vout = txInput.vout[result.getrawtransaction.vin[txInputIndex].vout]; | |||
tr | |||
th #{(txInputIndex + 1)} | |||
//pre | |||
// code #{JSON.stringify(txInput)} | |||
td | |||
if (vout.scriptPubKey && vout.scriptPubKey.addresses) | |||
span(class="monospace") #{vout.scriptPubKey.addresses[0]} | |||
br | |||
span(class="monospace text-muted") via tx | |||
a(href=("/tx/" + txInput.txid + "#output-" + result.getrawtransaction.vin[txInputIndex].vout), class="monospace") #{txInput.txid.substring(0, 14)}..., Output ##{result.getrawtransaction.vin[txInputIndex].vout + 1} | |||
td | |||
if (vout.value) | |||
span(class="monospace") Σ #{vout.value} | |||
tr | |||
td | |||
td | |||
td | |||
strong(class="monospace") Σ #{totalInputValue} | |||
div(class="col-md-6") | |||
table(class="table mb-0") | |||
thead | |||
tr | |||
th | |||
th Output | |||
th Amount | |||
tbody | |||
each vout, voutIndex in result.getrawtransaction.vout | |||
tr | |||
th #{(voutIndex + 1)} | |||
td | |||
if (vout.scriptPubKey) | |||
if (vout.scriptPubKey.addresses) | |||
a(id="output-" + voutIndex) | |||
span(class="monospace") #{vout.scriptPubKey.addresses[0]} | |||
else if (vout.scriptPubKey.hex && vout.scriptPubKey.hex.startsWith('6a24aa21a9ed')) | |||
span(class="monospace") Segregated Witness committment - | |||
a(href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#commitment-structure") docs | |||
i(class="fa fa-external-link") | |||
else if (vout.scriptPubKey.asm && vout.scriptPubKey.asm.startsWith('OP_RETURN ')) | |||
span(class="monospace") OP_RETURN: | |||
span(class="monospace text-muted") #{utils.hex2ascii(vout.scriptPubKey.asm.substring("OP_RETURN ".length))} | |||
td | |||
span(class="monospace") Σ #{vout.value} | |||
tr | |||
td | |||
td | |||
td | |||
strong(class="monospace") Σ #{totalOutputValue} | |||
div(id="tab-scripts", class="tab-pane", role="tabpanel") | |||
h3 Input Scripts | |||
table(class="table table-striped") | |||
thead | |||
tr | |||
th(style="width: 50px;") | |||
th Script Sig (asm) | |||
tbody | |||
each vin, vinIndex in result.getrawtransaction.vin | |||
tr | |||
th #{vinIndex + 1} | |||
td | |||
if (vin.scriptSig && vin.scriptSig.asm) | |||
span(class="word-wrap monospace") #{vin.scriptSig.asm} | |||
else if (vin.coinbase) | |||
div(style="line-height: 1.75em;") | |||
span(class="tag") coinbase | |||
br | |||
span(class="word-wrap monospace") #{vin.coinbase} | |||
br | |||
span(class="word-wrap monospace text-muted") (decoded) #{utils.hex2ascii(vin.coinbase)} | |||
h3 Output Scripts | |||
table(class="table table-striped") | |||
thead | |||
tr | |||
th(style="width: 50px;") | |||
th Script Pub Key (asm) | |||
tbody | |||
each vout, voutIndex in result.getrawtransaction.vout | |||
tr | |||
th #{voutIndex + 1} | |||
td | |||
if (vout.scriptPubKey && vout.scriptPubKey.asm) | |||
span(class="word-wrap monospace") #{vout.scriptPubKey.asm} | |||
if (vout.scriptPubKey.asm.startsWith("OP_RETURN")) | |||
br | |||
span(class="word-wrap monospace text-muted") (decoded) #{utils.hex2ascii(vout.scriptPubKey.asm)} | |||
div(id="tab-raw", class="tab-pane", role="tabpanel") | |||
div(class="highlight") | |||
pre | |||
code(class="language-json", data-lang="json") #{JSON.stringify(result.getrawtransaction, null, 4)} | |||
//pre #{JSON.stringify(result.txInputs, null, 4)} | |||