@ -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; |
@ -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"
|
|||
// }
|
|||
//}
|
|||
}; |
@ -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 |
|||
}; |
@ -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 |
|||
}; |
@ -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); |
|||
}); |
@ -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" |
|||
} |
|||
} |
@ -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; |
|||
} |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.5 KiB |
@ -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> |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
@ -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" |
|||
} |
|||
] |
|||
} |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 693 B |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 2.4 KiB |
@ -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; |
@ -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 |
@ -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 |
@ -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") |
@ -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} |
@ -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)} |
@ -0,0 +1,26 @@ |
|||
- var pageNumbers = []; |
|||
- for (var x = 1; x <= pageCount; x++) { |
|||
- pageNumbers.push(x); |
|||
- } |
|||
|
|||
nav(aria-label="Page navigation") |
|||
ul(class="pagination pagination-lg justify-content-center") |
|||
li(class="page-item", class=(pageNumber == 1 ? "disabled" : false)) |
|||
a(class="page-link", href=(pageNumber == 1 ? "javascript:void(0)" : paginationUrlFunction(pageNumber - 1)), aria-label="Previous") |
|||
span(aria-hidden="true") « |
|||
each x, xIndex in pageNumbers |
|||
if (x >= (pageNumber - 4) && x <= (pageNumber + 4) || xIndex == 0 || xIndex == (pageNumbers.length - 1)) |
|||
li(class="page-item", class=(x == pageNumber ? "active" : false)) |
|||
a(class="page-link", href=(paginationUrlFunction(x))) #{x} |
|||
|
|||
if (x == 1 && pageNumber > 6) |
|||
li(class="page-item disabled") |
|||
a(class="page-link", href="javascript:void(0)") ... |
|||
|
|||
else if (x == (pageCount - 1) && pageNumber < (pageCount - 5)) |
|||
li(class="page-item disabled") |
|||
a(class="page-link", href="javascript:void(0)") ... |
|||
|
|||
li(class="page-item", class=(pageNumber == pageCount ? "disabled" : false)) |
|||
a(class="page-link", href=(pageNumber == pageCount ? "javascript:void(0)" : paginationUrlFunction(pageNumber + 1)), aria-label="Next") |
|||
span(aria-hidden="true") » |
@ -0,0 +1,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)} |
@ -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 |
@ -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; |
|||
}); |
|||
}); |
@ -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)} |
|||
|
|||
|