Browse Source

first commit

master
coldstar 2 years ago
commit
f34a027081
45 changed files with 5337 additions and 0 deletions
  1. +59
    -0
      .gitignore
  2. +21
    -0
      LICENSE
  3. +15
    -0
      README.md
  4. +131
    -0
      app.js
  5. +16
    -0
      app/env.js
  6. +446
    -0
      app/rpcApi.js
  7. +103
    -0
      app/utils.js
  8. +31
    -0
      bin/www
  9. +22
    -0
      docs/Server-Setup.md
  10. +32
    -0
      docs/btc-explorer.com.conf
  11. +2766
    -0
      package-lock.json
  12. +30
    -0
      package.json
  13. +113
    -0
      proglog.txt
  14. +179
    -0
      public/css/styling.css
  15. BIN
      public/img/favicons/android-chrome-192x192.png
  16. BIN
      public/img/favicons/android-chrome-512x512.png
  17. BIN
      public/img/favicons/apple-touch-icon.png
  18. +9
    -0
      public/img/favicons/browserconfig.xml
  19. BIN
      public/img/favicons/favicon-16x16.png
  20. BIN
      public/img/favicons/favicon-32x32.png
  21. BIN
      public/img/favicons/favicon.ico
  22. +19
    -0
      public/img/favicons/manifest.json
  23. BIN
      public/img/favicons/mstile-150x150.png
  24. BIN
      public/img/favicons/vivobluelogo.png
  25. BIN
      public/img/icon.png
  26. BIN
      public/img/logo-256.png
  27. BIN
      public/img/logo/logo-64.png
  28. BIN
      public/img/logo/logo-64a.png
  29. BIN
      public/img/logo/vivobluelogo.png
  30. +367
    -0
      routes/baseActionsRouter.js
  31. +10
    -0
      views/block-height.pug
  32. +12
    -0
      views/block.pug
  33. +35
    -0
      views/blocks.pug
  34. +1
    -0
      views/btcprice.pug
  35. +37
    -0
      views/connect.pug
  36. +14
    -0
      views/error.pug
  37. +245
    -0
      views/includes/block-content.pug
  38. +22
    -0
      views/includes/blocks-list.pug
  39. +26
    -0
      views/includes/pagination.pug
  40. +31
    -0
      views/index.pug
  41. +74
    -0
      views/layout.pug
  42. +75
    -0
      views/mempool.pug
  43. +75
    -0
      views/node-info.pug
  44. +54
    -0
      views/terminal.pug
  45. +267
    -0
      views/transaction.pug

+ 59
- 0
.gitignore View File

@@ -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


+ 21
- 0
LICENSE View File

@@ -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.

+ 15
- 0
README.md View File

@@ -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)

+ 131
- 0
app.js View File

@@ -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;

+ 16
- 0
app/env.js View File

@@ -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"
}
}
};

+ 446
- 0
app/rpcApi.js View File

@@ -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
};

+ 103
- 0
app/utils.js View File

@@ -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
};

+ 31
- 0
bin/www View File

@@ -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);
});


+ 22
- 0
docs/Server-Setup.md View File

@@ -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"

+ 32
- 0
docs/btc-explorer.com.conf View File

@@ -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;
}
}

+ 2766
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 30
- 0
package.json View File

@@ -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"
}

+ 113
- 0
proglog.txt View File

@@ -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

+ 179
- 0
public/css/styling.css View File

@@ -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;
}

BIN
public/img/favicons/android-chrome-192x192.png View File

Before After
Width: 192  |  Height: 192  |  Size: 25KB

BIN
public/img/favicons/android-chrome-512x512.png View File

Before After
Width: 512  |  Height: 512  |  Size: 120KB

BIN
public/img/favicons/apple-touch-icon.png View File

Before After
Width: 180  |  Height: 180  |  Size: 23KB

+ 9
- 0
public/img/favicons/browserconfig.xml View File

@@ -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>

BIN
public/img/favicons/favicon-16x16.png View File

Before After
Width: 16  |  Height: 16  |  Size: 721B

BIN
public/img/favicons/favicon-32x32.png View File

Before After
Width: 32  |  Height: 32  |  Size: 1.7KB

BIN
public/img/favicons/favicon.ico View File

Before After

+ 19
- 0
public/img/favicons/manifest.json View File

@@ -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"
}

BIN
public/img/favicons/mstile-150x150.png View File

Before After
Width: 270  |  Height: 270  |  Size: 16KB

BIN
public/img/favicons/vivobluelogo.png View File

Before After
Width: 178  |  Height: 178  |  Size: 23KB

BIN
public/img/icon.png View File

Before After
Width: 16  |  Height: 16  |  Size: 1.4KB

BIN
public/img/logo-256.png View File

Before After
Width: 256  |  Height: 256  |  Size: 41KB

BIN
public/img/logo/logo-64.png View File

Before After
Width: 178  |  Height: 178  |  Size: 20KB

BIN
public/img/logo/logo-64a.png View File

Before After
Width: 64  |  Height: 64  |  Size: 5.5KB

BIN
public/img/logo/vivobluelogo.png View File

Before After
Width: 178  |  Height: 178  |  Size: 23KB

+ 367
- 0
routes/baseActionsRouter.js View File

@@ -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;

+ 10
- 0
views/block-height.pug View File

@@ -0,0 +1,10 @@
extends layout

block headContent
title Block #{blockHeight}

block content
h1(class="h2") Block #{blockHeight}
hr

include includes/block-content.pug

+ 12
- 0
views/block.pug View File

@@ -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

+ 35
- 0
views/blocks.pug View File

@@ -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

+ 1
- 0
views/btcprice.pug View File

@@ -0,0 +1 @@
td(class="monospace") 0000002

+ 37
- 0
views/connect.pug View File

@@ -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")

+ 14
- 0
views/error.pug View File

@@ -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}

+ 245
- 0
views/includes/block-content.pug View File

@@ -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)}

+ 22
- 0
views/includes/blocks-list.pug View File

@@ -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()}

+ 26
- 0
views/includes/pagination.pug View File

@@ -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") &laquo;
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") &raquo;

+ 31
- 0
views/index.pug View File

@@ -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)
[&copy; 2021 Vivocoin](http://vivocoin.net/)

+ 74
- 0
views/layout.pug View File

@@ -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

+ 75
- 0
views/mempool.pug View File

@@ -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}

+ 75
- 0
views/node-info.pug View File

@@ -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}

+ 54
- 0
views/terminal.pug View File

@@ -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;
});
});

+ 267
- 0
views/transaction.pug View File

@@ -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)}


Loading…
Cancel
Save