Browse Source

version 1.0

fix-133-memory-crash
Dan Janosik 8 years ago
parent
commit
8444cd69a6
  1. 130
      app.js
  2. 15
      app/env.js
  3. 198
      app/rpcApi.js
  4. 55
      app/utils.js
  5. 9
      bin/www
  6. 30
      package.json
  7. 37
      public/css/styling.css
  8. BIN
      public/img/favicons/android-icon-144x144.png
  9. BIN
      public/img/favicons/android-icon-192x192.png
  10. BIN
      public/img/favicons/android-icon-36x36.png
  11. BIN
      public/img/favicons/android-icon-48x48.png
  12. BIN
      public/img/favicons/android-icon-72x72.png
  13. BIN
      public/img/favicons/android-icon-96x96.png
  14. BIN
      public/img/favicons/apple-icon-114x114.png
  15. BIN
      public/img/favicons/apple-icon-120x120.png
  16. BIN
      public/img/favicons/apple-icon-144x144.png
  17. BIN
      public/img/favicons/apple-icon-152x152.png
  18. BIN
      public/img/favicons/apple-icon-180x180.png
  19. BIN
      public/img/favicons/apple-icon-57x57.png
  20. BIN
      public/img/favicons/apple-icon-60x60.png
  21. BIN
      public/img/favicons/apple-icon-72x72.png
  22. BIN
      public/img/favicons/apple-icon-76x76.png
  23. BIN
      public/img/favicons/apple-icon-precomposed.png
  24. BIN
      public/img/favicons/apple-icon.png
  25. 2
      public/img/favicons/browserconfig.xml
  26. BIN
      public/img/favicons/favicon-16x16.png
  27. BIN
      public/img/favicons/favicon-32x32.png
  28. BIN
      public/img/favicons/favicon-96x96.png
  29. BIN
      public/img/favicons/favicon.ico
  30. 41
      public/img/favicons/manifest.json
  31. BIN
      public/img/favicons/ms-icon-144x144.png
  32. BIN
      public/img/favicons/ms-icon-150x150.png
  33. BIN
      public/img/favicons/ms-icon-310x310.png
  34. BIN
      public/img/favicons/ms-icon-70x70.png
  35. BIN
      public/img/icon.png
  36. BIN
      public/img/logo-256.png
  37. BIN
      public/img/logo.psd
  38. BIN
      public/img/logo/logo-64.png
  39. 209
      routes/baseActionsRouter.js
  40. 19
      views/block-height.pug
  41. 19
      views/block.pug
  42. 37
      views/connect.pug
  43. 21
      views/error.pug
  44. 227
      views/includes/block-content.pug
  45. 26
      views/includes/pagination.pug
  46. 46
      views/index.pug
  47. 63
      views/layout.pug
  48. 54
      views/terminal.pug
  49. 271
      views/transaction.pug

130
app.js

@ -0,0 +1,130 @@
#!/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 md5 = require("md5");
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 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;
if (env.bitcoind && env.bitcoind.rpc) {
req.session.host = env.bitcoind.host;
req.session.port = env.bitcoind.port;
req.session.username = env.bitcoind.rpc.username;
global.client = new bitcoin.Client({
host: env.bitcoind.host,
port: env.bitcoind.port,
user: env.bitcoind.rpc.username,
pass: env.bitcoind.rpc.password,
timeout: 30000
});
}
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;

15
app/env.js

@ -0,0 +1,15 @@
module.exports = {
cookiePassword: "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
// Uncomment "bitcoind" below to automatically connect via RPC.
// Otherwise, you can manually connect via the UI.
//bitcoind:{
// host:"192.168.1.100",
// port:8332,
// rpc: {
// username:"username",
// password:"password"
// }
//}
};

198
app/rpcApi.js

@ -0,0 +1,198 @@
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 getBlockByHeight(blockHeight) {
console.log("getBlockByHeight: " + blockHeight);
return new Promise(function(resolve, reject) {
var client = global.client;
client.cmd('getblockhash', blockHeight, function(err, result, resHeaders) {
if (err) {
return console.log("Error 0928317yr3w: " + err);
}
client.cmd('getblock', result, function(err2, result2, resHeaders2) {
if (err2) {
return console.log("Error 320fh7e0hg: " + err2);
}
resolve({ success:true, getblockhash:result, getblock:result2 });
});
});
});
}
function getTransactionInputs(rpcClient, transaction) {
console.log("getTransactionInputs: " + transaction.txid);
return new Promise(function(resolve, reject) {
var txids = [];
for (var i = 0; i < transaction.vin.length; i++) {
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);
}
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]);
});
return;
}
var batch = [];
for (var i = 0; i < txids.length; i++) {
var txid = txids[i];
batch.push({
method: 'getrawtransaction',
params: [ txid, 1 ]
});
}
var results = [];
var count = batch.length;
client.cmd(batch, function(err, result, resHeaders) {
if (err) {
console.log("Error 10238rhwefyhd: " + err);
}
results.push(result);
count--;
if (count == 0) {
resolve(results);
}
});
});
}
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));
}
}
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 = {
getBlockByHeight: getBlockByHeight,
getTransactionInputs: getTransactionInputs,
getBlockData: getBlockData,
getRawTransaction: getRawTransaction,
getRawTransactions: getRawTransactions
};

55
app/utils.js

@ -0,0 +1,55 @@
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 eras = [ new Decimal8(50) ];
for (var i = 1; i < 34; i++) {
var previous = eras[i - 1];
eras.push(new Decimal8(previous).dividedBy(2));
}
var index = Math.floor(blockHeight / 210000);
return eras[index];
}
module.exports = {
doSmartRedirect: doSmartRedirect,
redirectToConnectPageIfNeeded: redirectToConnectPageIfNeeded,
hex2ascii: hex2ascii,
getBlockReward: getBlockReward
};

9
bin/www

@ -0,0 +1,9 @@
#!/usr/bin/env node
var debug = require('debug')('my-application');
var app = require('../app');
app.set('port', process.env.PORT || 3002);
var server = app.listen(app.get('port'), function() {
debug('Express server listening on port ' + server.address().port);
});

30
package.json

@ -0,0 +1,30 @@
{
"name": "btc-rpc-explorer",
"version": "1.0.0",
"private": false,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"body-parser": "~1.16.0",
"cookie-parser": "~1.4.3",
"bitcoin": "3.0.1",
"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",
"md5":"2.2.1",
"moment": "^2.18.1",
"monk": "^4.0.0",
"morgan": "~1.7.0",
"mysql": "2.13.0",
"nodemailer": "4.0.1",
"pug": "2.0.0-rc.2",
"scrypt": "6.0.3",
"sequelize": "3.30.4",
"serve-favicon": "~2.3.2",
"simple-git": "1.73.0"
}
}

37
public/css/styling.css

@ -0,0 +1,37 @@
body {
font: 14px 'Open Sans', "Lucida Grande", Helvetica, Arial, sans-serif;
}
hr {
margin: 15px 0;
}
img.header-image {
margin-top: -10px;
margin-bottom: -5px;
width: 30px;
height: 30px;
margin-right: 10px;
}
.monospace {
font-family: 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;
}

BIN
public/img/favicons/android-icon-144x144.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
public/img/favicons/android-icon-192x192.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
public/img/favicons/android-icon-36x36.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
public/img/favicons/android-icon-48x48.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/img/favicons/android-icon-72x72.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/img/favicons/android-icon-96x96.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
public/img/favicons/apple-icon-114x114.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/img/favicons/apple-icon-120x120.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
public/img/favicons/apple-icon-144x144.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
public/img/favicons/apple-icon-152x152.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/img/favicons/apple-icon-180x180.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
public/img/favicons/apple-icon-57x57.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
public/img/favicons/apple-icon-60x60.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
public/img/favicons/apple-icon-72x72.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/img/favicons/apple-icon-76x76.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
public/img/favicons/apple-icon-precomposed.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
public/img/favicons/apple-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

2
public/img/favicons/browserconfig.xml

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/img/favicons/favicon-96x96.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
public/img/favicons/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

41
public/img/favicons/manifest.json

@ -0,0 +1,41 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

BIN
public/img/favicons/ms-icon-144x144.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
public/img/favicons/ms-icon-150x150.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
public/img/favicons/ms-icon-310x310.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
public/img/favicons/ms-icon-70x70.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/img/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
public/img/logo-256.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
public/img/logo.psd

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

209
routes/baseActionsRouter.js

@ -0,0 +1,209 @@
var express = require('express');
var router = express.Router();
var util = require('util');
var moment = require('moment');
var utils = require('./../app/utils');
var md5 = require("md5");
var env = require("./../app/env");
var bitcoin = require("bitcoin");
var rpcApi = require("./../app/rpcApi")
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;
client.cmd('getinfo', function(err, result, resHeaders) {
if (err) {
return console.log(err);
}
res.locals.result = result;
var promises = [];
if (result.blocks) {
for (var i = 0; i < 10; i++) {
promises.push(rpcApi.getBlockByHeight(result.blocks - i));
}
}
Promise.all(promises).then(function() {
res.locals.latestBlocks = arguments[0];
res.render("index");
});
});
});
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("/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) {
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;
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 = {};
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) {
res.render("terminal");
});
router.post("/terminal", function(req, res) {
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;

19
views/block-height.pug

@ -0,0 +1,19 @@
extends layout
block headContent
title Block #{blockHeight}
block content
ol(class="breadcrumb")
li(class="breadcrumb-item")
a(href="/")
strong #{host}
span :#{port}
li(class="breadcrumb-item active")
a(href=("/block-height/" + blockHeight)) Block #{blockHeight}
h1(class="h2") Block
small ##{blockHeight}
hr
include includes/block-content.pug

19
views/block.pug

@ -0,0 +1,19 @@
extends layout
block headContent
title Block #{blockHash}
block content
ol(class="breadcrumb")
li(class="breadcrumb-item")
a(href="/")
strong #{host}
span :#{port}
li(class="breadcrumb-item active")
a(href=("/block/" + blockHash)) Block #{result.getblock.height}
h1 Block
small(style="width: 100%;") ##{result.getblock.height}
hr
include includes/block-content.pug

37
views/connect.pug

@ -0,0 +1,37 @@
extends layout
block content
h1 BTC RPC Explorer
hr
:markdown-it
This tool is intended to be a simple, stateless, self-hosted explorer for the Bitcoin blockchain, driven by RPC calls to your own bitcoind 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 bitcoind 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-block")
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")

21
views/error.pug

@ -0,0 +1,21 @@
extends layout
block content
ol(class="breadcrumb")
li(class="breadcrumb-item")
a(href="/")
strong #{host}
span :#{port}
li(class="breadcrumb-item active") Error
h1 Error
hr
if (message)
p !{message}
else
p Unknown error
if (error)
h2 #{error.status}
pre #{error.stack}

227
views/includes/block-content.pug

@ -0,0 +1,227 @@
ul(class='nav nav-tabs')
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
hr
- var txCount = result.getblock.tx.length;
div(class="tab-content")
div(id="tab-summary", class="tab-pane active", role="tabpanel")
if (result.getblock.hash == "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")
div(class="alert alert-success", style="padding-bottom: 0;")
h4(class="alert-heading h5") This is the Bitcoin Genesis Block!
:markdown-it
This is the first block in the Bitcoin blockchain. This block was mined by Bitcoin's anonymous/pseudonymous creator Satoshi Nakamoto. If you're interested, you can [read more about the genesis block](https://en.bitcoin.it/wiki/Genesis_block).
table(class="table")
tr
th(class="table-active properties-header") Block Hash
td
a(href=("/block/" + result.getblock.hash)) #{result.getblock.hash}
tr
th(class="table-active properties-header") Previous Block Hash
td
if (result.getblock.previousblockhash)
a(href=("/block/" + result.getblock.previousblockhash)) #{result.getblock.previousblockhash}
else if (result.getblock.hash == "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")
span N/A - This is the
a(href="https://en.bitcoin.it/wiki/Genesis_block") Bitcoin Genesis Block
tr
th(class="table-active properties-header") Next Block Hash
td
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
a(href=("/block-height/" + result.getblock.height)) #{result.getblock.height}
tr
th(class="table-active properties-header") Timestamp
td #{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 #{result.getblock.tx.length.toLocaleString()}
tr
th(class="table-active properties-header") Size
td
span #{result.getblock.size.toLocaleString()} bytes
br
span(class="text-muted") (weight: #{result.getblock.weight.toLocaleString()})
tr
th(class="table-active properties-header") Confirmations
td
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
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 0x#{result.getblock.versionHex}
span(class="text-muted") (decimal: #{result.getblock.version})
tr
th(class="table-active text-right") Nonce
td #{result.getblock.nonce}
tr
th(class="table-active text-right") Bits
td #{result.getblock.bits}
tr
th(class="table-active text-right") Merkle Root
td #{result.getblock.merkleroot}
tr
th(class="table-active text-right") Chainwork
td #{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}
div(class="card-block")
//pre
// code #{JSON.stringify(result.txInputsByTransaction[tx.txid], null, 4)}
if (true)
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 BTC
td #{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}
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")
td
span(class="monospace") #{vout.value}
- totalOutputValue = totalOutputValue.plus(vout.value);
tr
td
td
td
strong(class="monospace") #{totalOutputValue}
//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)}

26
views/includes/pagination.pug

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

46
views/index.pug

@ -0,0 +1,46 @@
extends layout
block headContent
title Home
block content
ol(class="breadcrumb")
li(class="breadcrumb-item")
a(href="/")
strong #{host}
span :#{port}
h1 BTC RPC Explorer
hr
ul(class='nav nav-tabs')
li(class="nav-item")
a(data-toggle="tab", href="#tab-latest-tx", class="nav-link active", role="tab") Latest Blocks
li(class="nav-item")
a(data-toggle="tab", href="#tab-getinfo", class="nav-link", role="tab") Node Info
hr
div(class="tab-content")
div(id="tab-latest-tx", class="tab-pane active", role="tabpanel")
h3 Latest Blocks
table(class="table table-striped")
thead
tr
th Height
th Timestamp (utc)
th Transactions
th Size (bytes)
tbody
each block in latestBlocks
tr
td
a(href=("/block-height/" + block.getblock.height)) #{block.getblock.height}
td #{moment.utc(new Date(parseInt(block.getblock.time) * 1000)).format("Y-MM-DD HH:mm:ss")}
td #{block.getblock.tx.length.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
td #{block.getblock.size.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
div(id="tab-getinfo", class="tab-pane", role="tabpanel")
h3 Node Info (getinfo)
pre
code #{JSON.stringify(result, null, 4)}

63
views/layout.pug

@ -0,0 +1,63 @@
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/3.3.7/css/bootstrap.min.css")
link(rel="stylesheet", href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css", integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ", 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")
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 BTC RPC Explorer
body
nav(class="navbar navbar-toggleable-md navbar-inverse bg-inverse mb-4")
div(class="container")
div(class="navbar-header")
button(type="button", class="navbar-toggler navbar-toggler-right", data-toggle="collapse", data-target="#navbarNav")
span(class="navbar-toggler-icon")
a(class="navbar-brand", href="/")
span
img(src="/img/logo/logo-64.png", class="header-image")
span BTC RPC Explorer
div(class="collapse navbar-collapse", id="navbarNav")
if (client)
ul(class="navbar-nav")
li(class="nav-item")
a(href="/terminal", class="nav-link") RPC Terminal
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/tether/1.4.0/js/tether.min.js", integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb", crossorigin="anonymous")
script(src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js", integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn", 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

54
views/terminal.pug

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

271
views/transaction.pug

@ -0,0 +1,271 @@
extends layout
block headContent
title Transaction #{txid}
style.
.field {
word-wrap: break-word;
}
block content
ol(class="breadcrumb")
li(class="breadcrumb-item")
a(href="/")
strong #{host}
span :#{port}
li(class="breadcrumb-item")
a(href=("/block/" + result.getrawtransaction.blockhash)) Block #{result.getblock.height}
li(class="breadcrumb-item active") Transaction
h1(class="h2") Transaction
br
small #{txid}
hr
ul(class='nav nav-tabs')
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
div(class="mb-3")
- 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 == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
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 [Bitcoin Genesis Block](/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f). 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 [Bitcoin Core](https://bitcoin.org) developers.
table(class="table")
tr
th(class="table-active properties-header") Included in Block
td
a(href=("/block/" + result.getrawtransaction.blockhash)) #{result.getrawtransaction.blockhash}
span(class="text-muted") (#{result.getblock.height})
tr
th(class="table-active properties-header") Timestamp
td #{moment.utc(new Date(result.getrawtransaction["time"] * 1000)).format("Y-MM-DD HH:mm:ss")} (utc)
//tr
// th(class="table-active properties-header") Transaction ID
// td #{txid}
tr
th(class="table-active properties-header") Version
td #{result.getrawtransaction.version}
tr
th(class="table-active properties-header") Size
td
span #{result.getrawtransaction.size.toLocaleString()} bytes
if (result.getrawtransaction.vsize != result.getrawtransaction.size)
span (
a(href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Transaction_size_calculations") virtual size
span : #{result.getrawtransaction.vsize})
if (result.getrawtransaction.locktime > 0)
tr
th(class="table-active properties-header")
span Locktime
td
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
if (result.getrawtransaction.confirmations == 0)
strong(class="text-danger") #{result.getrawtransaction.confirmations} (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 #{new Decimal(totalOutputValue).minus(totalInputValue)}
else
tr
th(class="table-active properties-header") Network Fee Paid
td
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-block")
h6 Hex
div(style="background-color: #f0f0f0; padding: 5px 10px;", class="mb-3")
span(class="monospace word-wrap") #{result.getrawtransaction.vin[0].coinbase}
h6 Decoded
div(style="background-color: #f0f0f0; 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-block")
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 BTC
td #{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")
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}
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