Massive commit for majority of v3.0:

- major visual refresh - new design, layout, fonts, colors, icons, etc
- new dark theme, now the default
- new app icon ("Bitcoin Map Marker")
- cleaner, simpler homepage content
- css for light/dark themes built from scss ("npm run css")
- theme-switcher button in footer, instead of checked items in header dropdown
- cleanup header links and dropdowns, and larger search box
- url change: /unconfirmed-tx -> /mempool-tx
- support for "estimated coin supply" used at startup or on slow devices when UTXO set unavailable
- display fee rate along with TX details on /mempool-tx
- copy-to-clipboard added for lots of longer strings
- major code reuse improvements with pug mixins
- simplify userSettings
- minor error handling improvements for /tx pages in prune/noTxIndex situations
- query fewer tx inputs on /address pages for performance
- exponents on /difficulty-history graph labels
- fixed colors of labels/gridlines on graphs (esp in dark theme)
- drop LTC config (no longer maintained)
- /admin/resetUserSettings
This commit is contained in:
Dan Janosik 2021-03-31 17:45:47 -04:00
parent 7595ab474e
commit 1b3264ef3c
No known key found for this signature in database
GPG key ID: C6F8CE9FFDB2CED2
90 changed files with 6879 additions and 4413 deletions

View file

@ -5,20 +5,24 @@
#DEBUG=btcexp:* # Enable all logging specific to btc-rpc-explorer
#DEBUG=btcexp:app,btcexp:error # Default
# The base URL. If you want to run it elsewhere than on /, for example /btc-rpc-explorer/ - note that the value should include starting and ending "/"
# Default: /
#BTCEXP_BASEURL=/
#BTCEXP_BASEURL=/explorer/
# The active coin. Only officially supported value is "BTC".
# Default: BTC
#BTCEXP_COIN=BTC
# Host/Port to bind to
# Defaults: shown
#BTCEXP_HOST=127.0.0.1
#BTCEXP_PORT=3002
# Bitcoin RPC Credentials (URI -OR- HOST/PORT/USER/PASS)
# Defaults:
# - [host/port]: 127.0.0.1:8332
@ -33,6 +37,7 @@
#BTCEXP_BITCOIND_COOKIE=/path/to/bitcoind/.cookie
#BTCEXP_BITCOIND_RPC_TIMEOUT=5000
# Select optional "address API" to display address tx lists and balances
# Options: electrumx, blockchain.com, blockchair.com, blockcypher.com
# If electrumx set, the BTCEXP_ELECTRUMX_SERVERS variable must also be
@ -40,91 +45,117 @@
# Default: none
#BTCEXP_ADDRESS_API=(electrumx|blockchain.com|blockchair.com|blockcypher.com)
# Optional ElectrumX Servers. See BTCEXP_ADDRESS_API. This value is only
# used if BTCEXP_ADDRESS_API=electrumx
#BTCEXP_ELECTRUMX_SERVERS=tls://electrumx.server.com:50002,tcp://127.0.0.1:50001,...
# Use the Electrumx server as an external txindex. This is only available in Electrs.
#BTCEXP_ELECTRUM_TXINDEX=true
# Set number of concurrent RPC requests. Should be lower than your node's "rpcworkqueue" value.
# Note that Bitcoin Core's default rpcworkqueue=16.
# Default: 10
#BTCEXP_RPC_CONCURRENCY=10
# Disable app's in-memory RPC caching to reduce memory usage
# Default: false (i.e. in-memory cache **enabled**)
#BTCEXP_NO_INMEMORY_RPC_CACHE=true
# Optional redis server for RPC caching
# Default: none
#BTCEXP_REDIS_URL=redis://localhost:6379
# Default: hash of credentials
#BTCEXP_COOKIE_SECRET=0000aaaafffffgggggg
# Whether public-demo aspects of the site are active
# Default: false
#BTCEXP_DEMO=true
# Set to false to enable resource-intensive features, including:
# UTXO set summary querying
# (default value is true, i.e. resource-intensive features are disabled)
#BTCEXP_SLOW_DEVICE_MODE=false
# Privacy mode disables:
# Exchange-rate queries, IP-geolocation queries
# Default: false
#BTCEXP_PRIVACY_MODE=true
# Don't request currency exchange rates
# Default: true (i.e. no exchange-rate queries made)
#BTCEXP_NO_RATES=true
# Password protection for site via basic auth (enter any username, only the password is checked)
# Default: none
#BTCEXP_BASIC_AUTH_PASSWORD=mypassword
# File where the SSO token is stored; ignored if BTCEXP_BASIC_AUTH_PASSWORD is provided.
# Enables SSO if present.
# Default: none
#BTCEXP_SSO_TOKEN_FILE=/var/run/btc-rpc-explorer/sso_token
# URL of an optional external SSO provider
# This is ignored if SSO is not enabled (see BTCEXP_SSO_TOKEN_FILE)
# Default: none
#BTCEXP_SSO_LOGIN_REDIRECT_URL=/my-sso-provider/login
# Enable to allow access to all RPC methods
# Default: false
#BTCEXP_RPC_ALLOWALL=true
# Custom RPC method blacklist
# Default: (see config.js)
#BTCEXP_RPC_BLACKLIST=signrawtransaction,sendtoaddress,stop,...
# Optional API keys
# Default: none
#BTCEXP_GANALYTICS_TRACKING=UA-XXXX-X
#BTCEXP_SENTRY_URL=https://00000fffffff@sentry.io/XXXX
#BTCEXP_IPSTACK_APIKEY=000000fffffaaaaa
# Optional value for "max_old_space_size"
# Default: 1024
#BTCEXP_OLD_SPACE_MAX_SIZE=2048
# The number of recent blocks to search for transactions when txindex is disabled
#BTCEXP_NOTXINDEX_SEARCH_DEPTH=3
# UI Theme
# Default: dark
#BTCEXP_UI_THEME=(dark|light)
# Show tools list in a sub-nav at top of screen
# Default: true
#BTCEXP_UI_SHOW_TOOLS_SUBHEADER=true
# Set the number of recent blocks shown on the homepage.
# For slow devices reduce this number.
# Default: 10
#BTCEXP_UI_HOME_PAGE_LATEST_BLOCKS_COUNT=10
# Set the number of blocks per page on the browse-blocks page.
# For slow devices reduce this number.
# Default: 50

View file

@ -1,11 +1,24 @@
##### Unreleased
##### v3.0
###### Unreleased
* Major visual refresh!
* All new design (layout, fonts, colors, etc)
* Redesigned Dark Mode (now the default)
* New app icon (this tools needs more of an identity of its own!)
* Support for pruned nodes and nodes with disabled `txindex`! (HUGE Thanks to [@shesek](https://github.com/shesek))
* Upgraded to Bootstrap 5 (currently beta3...)
* Fix for 404 pages hanging (Thanks [@shesek](https://github.com/shesek))
* Add convenience redirect for baseUrl (Thanks [@shesek](https://github.com/shesek))
* Make url in logs clickable (Thanks [@shesek](https://github.com/shesek))
* Caching for static files (maxAge=1hr)
* Major code refactoring/modernization
* Frontend performance optimizations
* Major refactoring, modernization, and code-reuse improvements
* UX improvements and polish throughout
* `/unconfirmed-tx` URL changed to `/mempool-tx`
* Updated dependencies
* jQuery: v3.4.1 -> v3.6.0
* highlight.js: v9.14.2 -> v10.7.1
* fontawesome: v5.7.1 -> v5.15.3
##### v2.2.0
###### 2021-01-22
@ -171,7 +184,7 @@
* Show mempool ancestor/descendant txs on tx detail pages
* Blacklist 'createwallet' by default
* Show RBF status for unconfirmed txs
* Faster, more reliable display of `/mempool-summary` and `/unconfirmed-tx` pages
* Faster, more reliable display of `/mempool-summary` and `/mempool-tx` pages
* Fix for persisting arg values in UI on `/rpc-browser`
* Misc minor fixes and ux tweaks

View file

@ -1,6 +1,6 @@
# BTC RPC Explorer
![homepage](https://github.com/janoside/btc-rpc-explorer/blob/master/public/img/screenshots/homepage.png?raw=true)
![homepage](./public/img/screenshots/homepage-v3.png)
[![npm version][npm-ver-img]][npm-ver-url] [![NPM downloads][npm-dl-img]][npm-dl-url]

61
app.js
View file

@ -54,6 +54,8 @@ const axios = require("axios");
const package_json = require('./package.json');
global.appVersion = package_json.version;
global.btcNodeSemver = "0.0.0";
const baseActionsRouter = require('./routes/baseRouter.js');
const apiActionsRouter = require('./routes/apiRouter.js');
@ -610,56 +612,27 @@ expressApp.use(function(req, res, next) {
res.locals.pageErrors = [];
if (!req.session.userSettings) {
req.session.userSettings = JSON.parse(req.cookies["user-settings"] || "{}");
}
const userSettings = req.session.userSettings;
res.locals.userSettings = userSettings;
// currency format type
if (!req.session.currencyFormatType) {
var cookieValue = req.cookies['user-setting-currencyFormatType'];
if (cookieValue) {
req.session.currencyFormatType = cookieValue;
} else {
req.session.currencyFormatType = "";
}
if (!userSettings.currencyFormatType) {
userSettings.currencyFormatType = "";
}
// theme
if (!req.session.uiTheme) {
var cookieValue = req.cookies['user-setting-uiTheme'];
if (cookieValue) {
req.session.uiTheme = cookieValue;
} else {
req.session.uiTheme = "";
}
if (!userSettings.uiTheme) {
userSettings.uiTheme = config.defaultTheme;
}
// blockPage.showTechSummary
if (!req.session.blockPageShowTechSummary) {
var cookieValue = req.cookies['user-setting-blockPageShowTechSummary'];
if (cookieValue) {
req.session.blockPageShowTechSummary = cookieValue;
} else {
req.session.blockPageShowTechSummary = "true";
}
}
// homepage banner
if (!req.session.hideHomepageBanner) {
var cookieValue = req.cookies['user-setting-hideHomepageBanner'];
if (cookieValue) {
req.session.hideHomepageBanner = cookieValue;
} else {
req.session.hideHomepageBanner = "false";
}
}
res.locals.currencyFormatType = req.session.currencyFormatType;
global.currencyFormatType = req.session.currencyFormatType;
res.locals.currencyFormatType = userSettings.currencyFormatType;
global.currencyFormatType = userSettings.currencyFormatType;
if (!["/", "/connect"].includes(req.originalUrl)) {
@ -727,7 +700,7 @@ expressApp.use(function(req, res, next) {
// development error handler
// will print stacktrace
if (expressApp.get('env') === 'development') {
if (expressApp.get("env") === "development" || expressApp.get("env") === "local") {
expressApp.use(function(err, req, res, next) {
if (err) {
utils.logError("3289023yege", err);

View file

@ -277,9 +277,9 @@ function getIndexInfo() {
return tryCacheThenRpcApi(miscCache, "getIndexInfo", 10 * ONE_SEC, rpcApi.getIndexInfo);
}
function getMempoolTxids() {
function getAllMempoolTxids() {
// no caching, that would be dumb
return rpcApi.getMempoolTxids();
return rpcApi.getAllMempoolTxids();
}
function getMiningInfo() {
@ -568,18 +568,16 @@ function getPeerSummary() {
});
}
function getMempoolDetails(start, count) {
function getMempoolTxids(limit, offset) {
return new Promise(function(resolve, reject) {
tryCacheThenRpcApi(miscCache, "getMempoolTxids", ONE_SEC, rpcApi.getMempoolTxids).then(function(resultTxids) {
tryCacheThenRpcApi(miscCache, "getMempoolTxids", ONE_SEC, rpcApi.getAllMempoolTxids).then(function(resultTxids) {
var txids = [];
for (var i = start; (i < resultTxids.length && i < (start + count)); i++) {
for (var i = offset; (i < resultTxids.length && i < (offset + limit)); i++) {
txids.push(resultTxids[i]);
}
getRawTransactionsWithInputs(txids, config.site.txMaxInput).then(function(result) {
resolve({ txCount:resultTxids.length, transactions:result.transactions, txInputsByTransaction:result.txInputsByTransaction });
});
resolve({ txCount:resultTxids.length, txids:txids });
}).catch(function(err) {
reject(err);
@ -1140,7 +1138,7 @@ module.exports = {
getNetworkInfo: getNetworkInfo,
getNetTotals: getNetTotals,
getMempoolInfo: getMempoolInfo,
getMempoolTxids: getMempoolTxids,
getAllMempoolTxids: getAllMempoolTxids,
getMiningInfo: getMiningInfo,
getIndexInfo: getIndexInfo,
getBlockByHeight: getBlockByHeight,
@ -1162,7 +1160,7 @@ module.exports = {
logCacheSizes: logCacheSizes,
getPeerSummary: getPeerSummary,
getChainTxStats: getChainTxStats,
getMempoolDetails: getMempoolDetails,
getMempoolTxids: getMempoolTxids,
getTxCountStats: getTxCountStats,
getSmartFeeEstimates: getSmartFeeEstimates,
getSmartFeeEstimate: getSmartFeeEstimate,

View file

@ -75,7 +75,7 @@ function getPeerInfo() {
return getRpcData("getpeerinfo");
}
function getMempoolTxids() {
function getAllMempoolTxids() {
return getRpcDataWithParams({method:"getrawmempool", parameters:[false]});
}
@ -553,7 +553,7 @@ module.exports = {
getNetworkInfo: getNetworkInfo,
getNetTotals: getNetTotals,
getMempoolInfo: getMempoolInfo,
getMempoolTxids: getMempoolTxids,
getAllMempoolTxids: getAllMempoolTxids,
getMiningInfo: getMiningInfo,
getIndexInfo: getIndexInfo,
getBlockByHeight: getBlockByHeight,

View file

@ -1,11 +1,9 @@
"use strict";
const btc = require("./coins/btc.js");
const ltc = require("./coins/ltc.js");
module.exports = {
"BTC": btc,
"LTC": ltc,
"coins":["BTC", "LTC"]
"coins":["BTC"]
};

View file

@ -55,10 +55,21 @@ module.exports = {
name:"Bitcoin",
ticker:"BTC",
logoUrlsByNetwork:{
"main":"./img/logo/logo.svg",
"test":"./img/logo/logo-testnet.svg",
"regtest":"./img/logo/logo-regtest.svg",
"signet":"./img/logo/logo-signet.svg"
},
coinIconUrlsByNetwork:{
"main":"./img/logo/btc.svg",
"test":"./img/logo/tbtc.svg",
"regtest":"./img/logo/tbtc.svg",
"signet":"./img/logo/signet.svg"
"test":"./img/logo/btc-testnet.svg",
"signet":"./img/logo/btc-signet.svg"
},
coinColorsByNetwork: {
"main": "#F7931A",
"test": "#1daf00",
"signet": "#af008c",
"regtest": "#777"
},
siteTitlesByNetwork: {
"main":"Bitcoin Explorer",
@ -69,7 +80,11 @@ module.exports = {
siteDescriptionHtml:"<b>BTC Explorer</b> is <a href='https://github.com/janoside/btc-rpc-explorer). If you run your own [Bitcoin Full Node](https://bitcoin.org/en/full-node), **BTC Explorer** can easily run alongside it, communicating via RPC calls. See the project [ReadMe](https://github.com/janoside/btc-rpc-explorer) for a list of features and instructions for running.",
nodeTitle:"Bitcoin Full Node",
nodeUrl:"https://bitcoin.org/en/full-node",
demoSiteUrl: "https://explorer.btc21.org",
demoSiteUrlsByNetwork: {
"main": "https://explorer.btc21.org",
"test": "https://testnet.btc21.org",
"signet": "https://signet.btc21.org",
},
miningPoolsConfigUrls:[
"https://raw.githubusercontent.com/btc21/Bitcoin-Known-Miners/master/miners.json",
"https://raw.githubusercontent.com/btccom/Blockchain-Known-Pools/master/pools.json",
@ -91,6 +106,21 @@ module.exports = {
baseCurrencyUnit:currencyUnits[3],
defaultCurrencyUnit:currencyUnits[0],
feeSatoshiPerByteBucketMaxima: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 50, 75, 100, 150],
halvingBlockIntervalsByNetwork: {
"main": 210000,
"test": 210000,
"regtest": 150,
"signet": 210000
},
// used for supply estimates that don't need full gettxoutset accuracy
coinSupplyCheckpointsByNetwork: {
"main": [ 675046, new Decimal(18656332.38) ],
"test": [ 1940614, new Decimal(20963051.112) ],
"signet": [ 29472, new Decimal(1473600) ]
},
genesisBlockHashesByNetwork:{
"main": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
"test": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
@ -391,7 +421,7 @@ module.exports = {
blockHeight: 0,
blockHash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
summary: "The Bitcoin Genesis Block.",
alertBodyHtml: "This is the first block in the Bitcoin blockchain, known as the 'Genesis Block'. This block was mined by Bitcoin's creator Satoshi Nakamoto. You can read more about <a href='https://en.bitcoin.it/wiki/Genesis_block'>the genesis block</a>.",
alertBodyHtml: "This is the first block in the Bitcoin blockchain, known as the <b>Genesis Block</b>. This block was mined by Bitcoin's creator <b>Satoshi Nakamoto</b>.<br/>Read more here: <a href='https://en.bitcoin.it/wiki/Genesis_block'>bitcoin.it/wiki/Genesis_block</a>.",
referenceUrl: "https://en.bitcoin.it/wiki/Genesis_block"
},
{
@ -401,7 +431,7 @@ module.exports = {
txid: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
blockHeight: 0,
summary: "The coinbase transaction of the Genesis Block.",
alertBodyHtml: "This transaction doesn't really exist! This is the coinbase transaction of the <a href='./block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'>Bitcoin Genesis Block</a>. For more background about this special-case transaction, you can read <a href='https://github.com/bitcoin/bitcoin/issues/3303'>this brief discussion</a> among some of the <a href='https://bitcoin.org'>Bitcoin</a> developers.",
alertBodyHtml: "This transaction doesn't really exist! This is the <b>coinbase transaction</b> of the <a href='./block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'>Bitcoin Genesis Block</a>. For more background about this special-case transaction, you can read <a href='https://github.com/bitcoin/bitcoin/issues/3303'>this brief discussion</a> among some of the <a href='https://bitcoin.org'>Bitcoin</a> developers.",
referenceUrl: "https://github.com/bitcoin/bitcoin/issues/3303"
},
{
@ -690,6 +720,7 @@ module.exports = {
date: "2013-03-24",
chain: "main",
blockHeight: 227835,
blockHash: "00000000000001aa077d7aa84c532a4d69bdbff519609d1da0835261b7a74eb6",
summary: "The last 'Version 1' block.",
alertBodyHtml: "This block was the last 'Version 1' block to be mined. <a href='https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki'>BIP-34</a> defined 'Version 2' blocks and the 'bip34' soft fork was officially locked in 96 blocks after this one, in block #<a href='./block-height/227931'>227,931</a>.",
referenceUrl: "https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki"

View file

@ -1,153 +0,0 @@
const Decimal = require("decimal.js");
const Decimal8 = Decimal.clone({ precision:8, rounding:8 });
const currencyUnits = [
{
type:"native",
name:"LTC",
multiplier:1,
default:true,
values:["", "ltc", "LTC"],
decimalPlaces:8
},
{
type:"native",
name:"lite",
multiplier:1000,
values:["lite"],
decimalPlaces:5
},
{
type:"native",
name:"photon",
multiplier:1000000,
values:["photon"],
decimalPlaces:2
},
{
type:"native",
name:"litoshi",
multiplier:100000000,
values:["litoshi", "lit"],
decimalPlaces:0
},
{
type:"exchanged",
name:"USD",
multiplier:"usd",
values:["usd"],
decimalPlaces:2,
symbol:"$"
},
];
module.exports = {
name:"Litecoin",
ticker:"LTC",
logoUrl:"/img/logo/ltc.svg",
siteTitle:"Litecoin Explorer",
nodeTitle:"Litecoin Full Node",
nodeUrl:"https://litecoin.org/",
demoSiteUrl: "https://ltc.chaintools.io",
miningPoolsConfigUrls:[
"https://raw.githubusercontent.com/hashstream/pools/master/pools.json",
],
maxBlockWeight: 4000000,
targetBlockTimeSeconds: 150,
currencyUnits:currencyUnits,
currencyUnitsByName:{"LTC":currencyUnits[0], "lite":currencyUnits[1], "photon":currencyUnits[2], "litoshi":currencyUnits[3]},
baseCurrencyUnit:currencyUnits[3],
defaultCurrencyUnit:currencyUnits[0],
feeSatoshiPerByteBucketMaxima: [5, 10, 25, 50, 100, 150, 200, 250],
genesisBlockHash: "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2",
genesisCoinbaseTransactionId: "97ddfbbae6be97fd6cdf3e7ca13232a3afff2353e29badfab7f73011edd4ced9",
genesisCoinbaseTransaction: {
"txid":"97ddfbbae6be97fd6cdf3e7ca13232a3afff2353e29badfab7f73011edd4ced9",
"hash":"97ddfbbae6be97fd6cdf3e7ca13232a3afff2353e29badfab7f73011edd4ced9",
"blockhash":"12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2",
"version":1,
"locktime":0,
"size":199,
"vsize":199,
"time":1317972665,
"blocktime":1317972665,
"vin":[
{
"prev_out":{
"hash":"0000000000000000000000000000000000000000000000000000000000000000",
"n":4294967295
},
"coinbase":"04ffff001d0104404e592054696d65732030352f4f63742f32303131205374657665204a6f62732c204170706c65e280997320566973696f6e6172792c2044696573206174203536"
}
],
"vout":[
{
"value":"50.00000000",
"n":0,
"scriptPubKey":{
"hex":"040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9 OP_CHECKSIG",
"type":"pubkey",
"reqSigs":1,
"addresses":[
"Ler4HNAEfwYhBmGXcFP2Po1NpRUEiK8km2"
]
}
}
]
},
historicalData: [
{
type: "blockheight",
date: "2011-10-07",
blockHeight: 0,
blockHash: "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2",
summary: "The Litecoin genesis block.",
alertBodyHtml: "This is the first block in the Litecoin blockchain.",
referenceUrl: "https://medium.com/@SatoshiLite/satoshilite-1e2dad89a017"
},
{
type: "tx",
date: "2017-05-10",
txid: "ce385e55fb2a73fa438426145b074f08314812fa3396472dc572b3079e26e0f9",
summary: "First SegWit transaction.",
referenceUrl: "https://twitter.com/satoshilite/status/862345830082138113"
},
{
type: "blockheight",
date: "2011-10-13",
blockHeight: 448,
blockHash: "6995d69ce2cb7768ef27f55e02dd1772d452deb44e1716bb1dd9c29409edf252",
summary: "The first block containing a (non-coinbase) transaction.",
referenceUrl: ""
},
{
type: "link",
date: "2016-05-02",
url: "/rpc-browser?method=verifymessage&args%5B0%5D=Ler4HNAEfwYhBmGXcFP2Po1NpRUEiK8km2&args%5B1%5D=G7W57QZ1jevRhBp7SajpcUgJiGs998R4AdBjcIgJq5BOECh4jHNatZKCFLQeo9PvZLf60ykR32XjT4IrUi9PtCU%3D&args%5B2%5D=I%2C+Charlie+Lee%2C+am+the+creator+of+Litecoin&execute=Execute",
summary: "Litecoin's Proof-of-Creator",
referenceUrl: "https://medium.com/@SatoshiLite/satoshilite-1e2dad89a017"
}
],
exchangeRateData:{
jsonUrl:"https://api.coinmarketcap.com/v1/ticker/Litecoin/",
exchangedCurrencyName:"usd",
responseBodySelectorFunction:function(responseBody) {
if (responseBody[0] && responseBody[0].price_usd) {
return {"usd":responseBody[0].price_usd};
}
return null;
}
},
blockRewardFunction:function(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 / 840000);
return eras[index];
}
};

View file

@ -19,6 +19,7 @@ const coins = require("./coins.js");
const credentials = require("./credentials.js");
const currentCoin = process.env.BTCEXP_COIN || "BTC";
const defaultTheme = process.env.BTCEXP_UI_THEME || "dark";
const rpcCred = credentials.rpc;
@ -84,6 +85,7 @@ module.exports = {
baseUrl: baseUrl,
coin: currentCoin,
defaultTheme: defaultTheme,
cookieSecret: cookieSecret,
@ -199,7 +201,6 @@ module.exports = {
{name: "Bitcoin Explorer", url:"https://explorer.btc21.org", imgUrl:"./img/logo/btc.svg"},
{name: "Testnet Explorer", url:"https://testnet.btc21.org", imgUrl:"./img/logo/tbtc.svg"},
{name: "Signet Explorer", url:"https://signet.btc21.org", imgUrl:"./img/logo/signet.svg"},
{name: "LND Admin", url:"https://lnd-admin.btc21.org", imgUrl:"./img/logo/lnd-admin.png"},
//{name: "Litecoin Explorer", url:"https://ltc.chaintools.io", imgUrl:"/img/logo/ltc.svg"},
//{name: "Lightning Explorer", url:"https://lightning.chaintools.io", imgUrl:"/img/logo/lightning.svg"},
]
@ -220,12 +221,12 @@ module.exports = {
/* 3 */ {name:"Transaction Stats", url:"./tx-stats", desc:"See graphs of total transaction volume and transaction rates.", fontawesome:"fas fa-chart-bar"},
/* 4 */ {name:"Mempool Summary", url:"./mempool-summary", desc:"Detailed summary of the current mempool for this node.", fontawesome:"fas fa-receipt"},
/* 5 */ {name:"Browse Pending Tx", url:"./unconfirmed-tx", desc:"Browse unconfirmed/pending transactions.", fontawesome:"fas fa-unlock"},
/* 5 */ {name:"Browse Mempool", url:"./mempool-tx", desc:"Browse unconfirmed/pending transactions.", fontawesome:"fas fa-unlock"},
/* 6 */ {name:"RPC Browser", url:"./rpc-browser", desc:"Browse the RPC functionality of this node. See docs and execute commands.", fontawesome:"fas fa-book"},
/* 7 */ {name:"RPC Terminal", url:"./rpc-terminal", desc:"Directly execute RPCs against this node.", fontawesome:"fas fa-terminal"},
/* 8 */ {name:(coins[currentCoin].name + " Fun"), url:"./fun", desc:"See fun/interesting historical blockchain data.", fontawesome:"fas fa-certificate"},
/* 8 */ {name:(coins[currentCoin].name + " Fun"), url:"./fun", desc:"See fun/interesting historical blockchain data.", fontawesome:"fas fa-flag"},
/* 9 */ {name:"Mining Summary", url:"./mining-summary", desc:"Summary of recent data about miners.", fontawesome:"fas fa-chart-pie"},
/* 10 */ {name:"Block Stats", url:"./block-stats", desc:"Summary data for blocks in configurable range.", fontawesome:"fas fa-layer-group"},

View file

@ -285,11 +285,11 @@ function formatValueInActiveCurrency(amount) {
}
function formatValueInGold(amount) {
function formatValueInGoldOunces(amount) {
var currencyFormat = global.currencyFormatType.toLowerCase() || "usd";
if (global.exchangeRates[currencyFormat] && global.goldExchangeRates[currencyFormat]) {
return formatExchangedCurrency(amount, "au");
return formatExchangedCurrency(amount, "au", false);
} else {
return formatExchangedCurrency(amount, "usd");
@ -339,17 +339,17 @@ function satoshisPerUnitOfActiveCurrency() {
}
}
function formatExchangedCurrency(amount, exchangeType) {
function formatExchangedCurrency(amount, exchangeType, includeUnit=true) {
if (global.exchangeRates != null && global.exchangeRates[exchangeType.toLowerCase()] != null) {
var dec = new Decimal(amount);
dec = dec.times(global.exchangeRates[exchangeType.toLowerCase()]);
var exchangedAmt = parseFloat(Math.round(dec * 100) / 100).toFixed(2);
if (exchangeType == "eur") {
return "€" + addThousandsSeparators(exchangedAmt);
return (includeUnit ? "€" : "") + addThousandsSeparators(exchangedAmt);
} else {
return "$" + addThousandsSeparators(exchangedAmt);
return (includeUnit ? "$" : "") + addThousandsSeparators(exchangedAmt);
}
} else if (exchangeType == "au") {
@ -358,7 +358,7 @@ function formatExchangedCurrency(amount, exchangeType) {
dec = dec.times(global.exchangeRates.usd).dividedBy(global.goldExchangeRates.usd);
var exchangedAmt = parseFloat(Math.round(dec * 100) / 100).toFixed(2);
return addThousandsSeparators(exchangedAmt) + "oz";
return addThousandsSeparators(exchangedAmt) + (includeUnit ? "oz" : "");
}
}
@ -384,6 +384,30 @@ function ellipsize(str, length, ending="…") {
}
}
function ellipsizeMiddle(str, length, replacement="…", extraCharAtStart=true) {
if (str.length <= length) {
return str;
} else {
//"abcde"(3)->"a…e"
//"abcdef"(3)->"a…f"
//"abcdef"(5)->"ab…ef"
//"abcdef"(4)->"ab…f"
if ((length - replacement.length) % 2 == 0) {
return str.substring(0, (length - replacement.length) / 2) + replacement + str.slice(-(length - replacement.length) / 2);
} else {
if (extraCharAtStart) {
return str.substring(0, Math.ceil((length - replacement.length) / 2)) + replacement + str.slice(-Math.floor((length - replacement.length) / 2));
} else {
return str.substring(0, Math.floor((length - replacement.length) / 2)) + replacement + str.slice(-Math.ceil((length - replacement.length) / 2));
}
}
}
}
function shortenTimeDiff(str) {
str = str.replace(" years", "y");
str = str.replace(" year", "y");
@ -525,6 +549,23 @@ function getBlockTotalFeesFromCoinbaseTxAndBlockHeight(coinbaseTx, blockHeight)
}
}
function estimatedSupply(height) {
var checkpointData = coinConfig.coinSupplyCheckpointsByNetwork[global.activeBlockchain];
var checkpointHeight = checkpointData[0];
var checkpointSupply = checkpointData[1];
var supply = checkpointSupply;
var i = checkpointHeight;
while (i < height) {
supply = supply.plus(new Decimal(coinConfig.blockRewardFunction(i, global.activeBlockchain)));
i++;
}
return supply;
}
function refreshExchangeRates() {
if (!config.queryExchangeRates || config.privacyMode) {
return;
@ -882,6 +923,17 @@ const dtMillis = (startTimeNanos) => {
return parseInt(dtNanos) * 1e-6;
};
function objectProperties(obj) {
const props = [];
for (const prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
props.push(prop);
}
}
return props;
}
module.exports = {
reflectPromise: reflectPromise,
redirectToConnectPageIfNeeded: redirectToConnectPageIfNeeded,
@ -894,7 +946,7 @@ module.exports = {
formatCurrencyAmountWithForcedDecimalPlaces: formatCurrencyAmountWithForcedDecimalPlaces,
formatExchangedCurrency: formatExchangedCurrency,
formatValueInActiveCurrency: formatValueInActiveCurrency,
formatValueInGold: formatValueInGold,
formatValueInGoldOunces: formatValueInGoldOunces,
satoshisPerUnitOfActiveCurrency: satoshisPerUnitOfActiveCurrency,
addThousandsSeparators: addThousandsSeparators,
formatCurrencyAmountInSmallestUnits: formatCurrencyAmountInSmallestUnits,
@ -903,6 +955,7 @@ module.exports = {
logMemoryUsage: logMemoryUsage,
getMinerFromCoinbaseTx: getMinerFromCoinbaseTx,
getBlockTotalFeesFromCoinbaseTxAndBlockHeight: getBlockTotalFeesFromCoinbaseTxAndBlockHeight,
estimatedSupply: estimatedSupply,
refreshExchangeRates: refreshExchangeRates,
parseExponentStringDouble: parseExponentStringDouble,
formatLargeNumber: formatLargeNumber,
@ -914,6 +967,7 @@ module.exports = {
logError: logError,
buildQrCodeUrls: buildQrCodeUrls,
ellipsize: ellipsize,
ellipsizeMiddle: ellipsizeMiddle,
shortenTimeDiff: shortenTimeDiff,
outputTypeAbbreviation: outputTypeAbbreviation,
outputTypeName: outputTypeName,
@ -924,5 +978,6 @@ module.exports = {
getCrawlerFromUserAgentString: getCrawlerFromUserAgentString,
timePromise: timePromise,
startTimeNanos: startTimeNanos,
dtMillis: dtMillis
dtMillis: dtMillis,
objectProperties: objectProperties
};

2968
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,15 @@
"bin": "bin/cli.js",
"scripts": {
"start": "node ./bin/www",
"refresh-mining-pool-configs": "node ./bin/refresh-mining-pool-configs.js"
"refresh-mining-pool-configs": "node ./bin/refresh-mining-pool-configs.js",
"css-light-debug": "node-sass --output-style expanded --source-map scss --precision 6 ./public/scss/light.scss ./public/style/light.css",
"css-dark-debug": "node-sass --output-style expanded --source-map scss --precision 6 ./public/scss/dark.scss ./public/style/dark.css",
"css-debug": "npm-run-all css-light-debug css-dark-debug",
"css-light": "node-sass --output-style compressed --precision 6 ./public/scss/light.scss ./public/style/light.css",
"css-dark": "node-sass --output-style compressed --precision 6 ./public/scss/dark.scss ./public/style/dark.css",
"css": "npm-run-all css-light css-dark"
},
"keywords": [
"bitcoin",
@ -27,6 +35,7 @@
"bitcoinjs-lib": "^5.2.0",
"bluebird": "^3.7.2",
"body-parser": "^1.19.0",
"bootstrap": "5.0.0-beta3",
"cookie-parser": "^1.4.5",
"crypto-js": "^4.0.0",
"csurf": "^1.11.0",
@ -41,25 +50,26 @@
"lru-cache": "6.0.0",
"markdown-it": "^12.0.4",
"md5": "^2.3.0",
"meow": "^5.0.0",
"meow": "^9.0.0",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"morgan": "^1.10.0",
"on-headers": "1.0.2",
"pidusage": "2.0.21",
"pug": "^3.0.0",
"pug": "^3.0.2",
"qrcode": "^1.4.4",
"redis": "^3.0.2",
"request": "^2.88.2",
"semver": "^7.3.4",
"semver": "^7.3.5",
"serve-favicon": "^2.5.0",
"simple-git": "^2.34.2"
"simple-git": "^2.37.0"
},
"optionalDependencies": {
"event-loop-stats": "^1.3.0"
},
"devDependencies": {
"less": "3.9.0",
"node-sass": "^5.0.0",
"npm-run-all": "^4.1.5"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,36 +0,0 @@
hr {
background-color: #555555;
}
.nav-tabs .nav-link.active {
border-color: #9a9b9c #9a9b9c #0c0c0c !important;
}
.data-tag {
color: #111 !important;
border-color: #cccccc !important;
}
.data-tag.bg-dark {
background-color: #cccccc !important;
}
.alert {
opacity: 0.8 !important;
}
hr {
border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
}
.hljs-attr {
color: #eeeeee;
}
.hljs, .hljs-subst {
color: #dddddd;
}
.hljs-type, .hljs-string, .hljs-number, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion {
color: #95dbc6;
}

View file

@ -1 +0,0 @@
.hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}

View file

@ -1,300 +0,0 @@
body {
font-size: 14px;
font-family: "Ubuntu", sans-serif;
/*font: 14px 'Open Sans', "Lucida Grande", Helvetica, Arial, sans-serif;*/
}
hr {
margin: 5px 0 15px 0;
}
img.header-image {
margin-top: -10px;
margin-bottom: -5px;
width: 30px;
height: 30px;
margin-right: 10px;
}
code, .text-monospace {
font-family: "Source Code Pro", monospace !important;
}
strong {
font-weight: 700 !important;
}
h6, .h6 {
margin-bottom: 0 !important;
}
.text-small {
font-size: 80%;
}
.popover {
max-width: 1200px;
}
.border-dotted {
border-bottom: 1px dotted #aaa;
}
.data-tag {
color: white;
border-radius: 0.25rem;
padding: 3px 6px;
}
.mb-tiny {
margin-bottom: 3px;
}
.mb-huge {
margin-bottom: 200px;
}
.user-message-markdown p {
margin-bottom: 0 !important;
}
.nav-tabs .nav-link.active {
background-color: transparent;
border-color: #dee2e6 #dee2e6 #f8f9fa;
}
pre {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
margin-bottom: 0;
}
dd {
margin-bottom: 1rem;
}
.word-wrap {
word-wrap: break-word;
word-break: break-all;
}
#subheader a {
margin-right: 20px;
}
.table th {
border-top: none;
}
.dropdown-item:focus, .dropdown-item:hover {
background-color: #e3e3e3;
}
#sub-menu a:hover {
text-decoration: underline;
}
strong {
font-weight: 500;
}
.xindex-summary table tbody th, .xindex-summary table tbody td {
padding: 0;
padding-top: 0.3rem;
padding-bottom: 0.3rem;
}
.index-summary .summary-icon {
width: 20px;
}
.summary-table-label, .summary-table-content, .summary-split-table-label, .summary-split-table-content {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 5px;
padding-left: 15px;
margin-bottom: 5px;
}
.tx-io-label, .tx-io-content, .tx-io-desc, .tx-io-value {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 0px;
padding-left: 0px;
margin-bottom: 2px;
float: left;
}
.summary-table-label, .summary-split-table-label {
font-weight: bold;
}
.summary-table-content, .summary-split-table-content {
margin-bottom: 20px;
}
@media (min-width: 576px) {
.summary-table-label {
max-width: 100%;
text-align: left;
}
.summary-table-content {
max-width: 100%;
margin-bottom: 20px;
}
.summary-split-table-label {
max-width: 100%;
text-align: left;
}
.summary-split-table-content {
max-width: 100%;
margin-bottom: 20px;
}
.tx-io-label { max-width: 100%; }
.tx-io-content { max-width: 100%; }
.tx-io-desc { max-width: 100%; }
.tx-io-value { max-width: 100%; }
}
@media (min-width: 768px) {
.summary-table-label {
max-width: 18%;
text-align: right;
}
.summary-table-content {
max-width: 82%;
margin-bottom: 5px;
}
.summary-split-table-label {
max-width: 36%;
text-align: right;
}
.summary-split-table-content {
max-width: 64%;
margin-bottom: 5px;
}
.tx-io-label { max-width: 8%; }
.tx-io-content { max-width: 92%; }
.tx-io-desc { max-width: 65%; }
.tx-io-value { max-width: 35%; text-align: right; }
}
@media (min-width: 992px) {
.summary-table-label {
max-width: 15%;
text-align: right;
}
.summary-table-content {
max-width: 85%;
margin-bottom: 5px;
}
.summary-split-table-label {
max-width: 30%;
text-align: right;
}
.summary-split-table-content {
max-width: 70%;
margin-bottom: 5px;
}
.tx-io-label { max-width: 11%; }
.tx-io-content { max-width: 89%; }
.tx-io-desc { max-width: 60%; }
.tx-io-value { max-width: 40%; text-align: right; }
}
@media (min-width: 1200px) {
.container {
max-width: 1160px;
}
.summary-table-label {
max-width: 13%;
text-align: right;
}
.summary-table-content {
max-width: 87%;
margin-bottom: 5px;
}
.summary-split-table-label {
max-width: 24%;
text-align: right;
}
.summary-split-table-content {
max-width: 76%;
margin-bottom: 5px;
}
.tx-io-label { max-width: 11%; }
.tx-io-content { max-width: 89%; }
.tx-io-desc { max-width: 69%; }
.tx-io-value { max-width: 31%; text-align: right; }
}
@media (min-width: 1920px) {
.container {
max-width: 1800px;
}
.summary-table-label {
max-width: 8%;
text-align: right;
}
.summary-table-content {
max-width: 92%;
margin-bottom: 5px;
}
.summary-split-table-label {
max-width: 16%;
text-align: right;
}
.summary-split-table-content {
max-width: 84%;
margin-bottom: 5px;
}
.tx-io-label { max-width: 7%; }
.tx-io-content { max-width: 93%; }
.tx-io-desc { max-width: 70%; }
.tx-io-value { max-width: 30%; text-align: right; }
}
footer a {
color: #ddd;
text-decoration: underline;
}
footer a:hover {
color: white;
}
.table-striped>tbody>tr:nth-child(odd)>td,
.table-striped>tbody>tr:nth-child(odd)>th {
/*background-color: #fbfbfb;*/
}
.hljs-type, .hljs-string, .hljs-number, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion {
color: #3aa54f;
}

View file

Before

Width:  |  Height:  |  Size: 956 B

After

Width:  |  Height:  |  Size: 956 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 956 B

After

Width:  |  Height:  |  Size: 956 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
<style type="text/css">
.st0{fill:#007bff;}
.st1{opacity:0.0;fill:#ffff00;}
.st2{fill:#ffcc00;}
</style>
<circle class="st0" cx="32" cy="32" r="32"/>
<path class="st1" d="M46.9,17.5C46.7,17.2,46.4,17,46,17H29c-0.4,0-0.7,0.2-0.9,0.6l-8,16c-0.2,0.3-0.1,0.7,0,1
c0.2,0.3,0.5,0.5,0.9,0.5h5.5l-8.4,18.6c-0.2,0.4-0.1,0.9,0.3,1.2c0.2,0.1,0.4,0.2,0.6,0.2c0.2,0,0.5-0.1,0.6-0.2l24-20
c0.3-0.3,0.4-0.7,0.3-1.1C43.8,33.3,43.4,33,43,33h-6.1l9.9-14.4C47,18.3,47.1,17.9,46.9,17.5z"/>
<path class="st2" d="M46.9,15.5C46.7,15.2,46.4,15,46,15H29c-0.4,0-0.7,0.2-0.9,0.6l-8,16c-0.2,0.3-0.1,0.7,0,1
c0.2,0.3,0.5,0.5,0.9,0.5h5.5l-8.4,18.6c-0.2,0.4-0.1,0.9,0.3,1.2c0.2,0.1,0.4,0.2,0.6,0.2c0.2,0,0.5-0.1,0.6-0.2l24-20
c0.3-0.3,0.4-0.7,0.3-1.1C43.8,31.3,43.4,31,43,31h-6.1l9.9-14.4C47,16.3,47.1,15.9,46.9,15.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -0,0 +1,7 @@
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path fill="#777" d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"></path>
<g transform="translate(-83, 40) rotate(-14 0 0) scale(14 14)">
<path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,7 @@
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path fill="#af008c" d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"></path>
<g transform="translate(-83, 40) rotate(-14 0 0) scale(14 14)">
<path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,7 @@
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path fill="#1daf00" d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"></path>
<g transform="translate(-83, 40) rotate(-14 0 0) scale(14 14)">
<path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

7
public/img/logo/logo.svg Normal file
View file

@ -0,0 +1,7 @@
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path fill="#F7931A" d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"></path>
<g transform="translate(-83, 40) rotate(-14 0 0) scale(14 14)">
<path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 B

View file

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<g fill="none" fill-rule="evenodd">
<circle cx="16" cy="16" r="16" fill="#BFBBBB"/>
<polygon fill="#FFF" points="10.427 19.214 9 19.768 9.688 17.009 11.132 16.429 13.213 8 18.342 8 16.823 14.196 18.233 13.625 17.553 16.375 16.126 16.946 15.278 20.429 23 20.429 22.127 24 9.252 24"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

7
public/js/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

655
public/js/highlight.pack.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,16 +4,23 @@ function updateCurrencyValue(element, val) {
}).done(function(result) {
element.html(result);
$('[data-toggle="tooltip"]').tooltip();
$('[data-bs-toggle="tooltip"]').tooltip();
});
}
function updateFeeRateValue(element, val, digits) {
function updateUserSetting(name, val) {
$.ajax({
url: `./changeSetting?name=${name}&value=${val}`
}).done(res => {});
}
function updateFeeRateValue(element, val, digits, showUnit=true) {
$.ajax({
url: `./api/utils/formatCurrencyAmountInSmallestUnits/${val},${digits}`
}).done(function(result) {
element.html(`<span>${result.val} <small>${result.currencyUnit}/vB</small></span>`);
element.html(`<span>${result.val}${showUnit ? ("<small class='ms-2'>" + result.currencyUnit + "/vB</small>") : ""}</span>`);
});
}
@ -24,4 +31,10 @@ function showAllTxOutputs(link, txid) {
});
link.classList.add("d-none");
}
function copyTextToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {}, (err) => {
console.error('Error copying text: ', err);
});
}

View file

@ -3,5 +3,5 @@ Disallow: /address/
Disallow: /rpc*
Disallow: /tx-stats
Disallow: /peers
Disallow: /unconfirmed-tx
Disallow: /mempool-tx
Disallow: /block-analysis/

132
public/scss/dark.scss Normal file
View file

@ -0,0 +1,132 @@
$body-bg: #031b40;
$body-color: #f7f9fa;
$main-bg: darken($body-bg, 6%);
$text-muted: #b7bfc7;
$dark: #3d454d;
/*$theme-colors: (
"dark": #292e33
);*/
$border-color: lighten($body-bg, 4%);
$card-border-color: $border-color;
$link-hover-color: #408cff;
$table-striped-bg: lighten($body-bg, 4%);
$pagination-bg: $body-bg;
$pagination-border-color: lighten($border-color, 5%);
$pagination-active-bg: $body-bg;
$pagination-active-border-color: lighten($border-color, 5%);
$pagination-hover-bg: lighten($pagination-bg, 15%);
$pagination-hover-border-color: lighten($border-color, 5%);
$pagination-disabled-bg: darken($pagination-bg, 3%);
$pagination-disabled-border-color: lighten($border-color, 5%);
$tooltip-bg: lighten($body-bg, 15%);
$nav-tabs-link-active-color: darken($body-color, 5%);
//$nav-tabs-link-active-bg: $body-bg !default;
$nav-tabs-link-active-border-color: #ced4da #ced4da $body-bg !important;
$btn-close-color: #fff;
$tooltip-opacity: 1.0;
$dropdown-bg: lighten($body-bg, 10%);
$dropdown-link-color: $body-color;
$dropdown-link-hover-bg: lighten($dropdown-bg, 10%);
$dropdown-link-hover-color: $body-color;
$dropdown-link-active-bg: lighten($dropdown-bg, 5%);
$dropdown-dark-bg: #ff0;
$dropdown-dark-link-hover-bg: #0ff;
$dropdown-dark-link-active-bg: #f0f;
$dropdown-link-active-color: lighten($dropdown-bg, 5%);
$dropdown-header-color: $body-color;
$dropdown-box-shadow: 0 1.5rem 1rem rgba(#000, .35);
$input-bg: darken(#fff, 0%);
//$input-border-color: lighten($input-bg, 9%);
$input-color: #212529;
@import "./main";
@import "../../node_modules/bootstrap/scss/bootstrap";
.alert-primary {
color: $body-color;
background-color: lighten($body-bg, 8%);
border-color: lighten($body-bg, 20%);
border-width: 2px;
}
.alert-danger {
color: $body-color;
background-color: #7d323b;
border-color: #7d1f29;
border-width: 2px;
}
.alert-danger a {
color: $body-color;
text-decoration: underline;
}
.alert-warning {
color: $body-color;
background-color: #d9962b;
border-color: darken(#d9962b, 10%);
border-width: 2px;
}
.bg-main {
background-color: $main-bg !important;
}
.bg-header-footer {
background-color: #162740 !important;
}
.bg-header-footer-highlight {
background-color: lighten(#162740, 15%) !important;
}
.bg-gradient-body-to-main {
background: linear-gradient(0deg, $main-bg 0%, $body-bg 100%);
}
.card-highlight {
background-color: lighten($card-bg, 5%);
border: solid 1px lighten($card-bg, 10%) !important;
color: $body-color;
}
code {
padding: 0px 3px;
border-radius: 3px;
background-color: lighten($card-bg, 15%);
color: #198754;
}

56
public/scss/light.scss Normal file
View file

@ -0,0 +1,56 @@
$body-bg: #fff;
$body-color: #212529;
$main-bg: #f2f5f7;
$border-color: #e4e7eb;
$card-border-color: $border-color;
$nav-tabs-border-color: #adb5bd;
$nav-tabs-link-active-color: lighten($body-color, 5%);
$nav-tabs-link-active-border-color: #adb5bd #adb5bd $body-bg !important;
$alert-bg-scale: -70%;
$alert-border-scale: -60%;
@import "./main";
@import "../../node_modules/bootstrap/scss/bootstrap";
.bg-main {
background-color: $main-bg !important;
}
.bg-header-footer {
background-color: #212529 !important;
}
.bg-header-footer-highlight {
background-color: lighten(#212529, 15%) !important;
}
.bg-header-footer {
background-color: #162740 !important;
}
.bg-header-footer-highlight {
background-color: lighten(#162740, 15%) !important;
}
.bg-gradient-body-to-main {
background: linear-gradient(0deg, $main-bg 0%, $body-bg 100%);
}
.card-highlight {
background-color: darken($card-bg, 3%);
border: solid 1px darken($card-bg, 10%) !important;
color: $body-color;
}

261
public/scss/main.scss Normal file
View file

@ -0,0 +1,261 @@
$spacer: 1rem;
$spacers: (
0: 0,
1: $spacer / 4,
2: $spacer / 2,
3: $spacer,
4: $spacer * 1.5,
5: $spacer * 2,
6: $spacer * 2.5,
7: $spacer * 3,
tiny: $spacer * 0.15
) !default;
$enable-negative-margins: true;
/*$grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px,
xxxl: 1900px
) !default;
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1320px,
xxxl: 1600px
) !default;*/
$btn-padding-y-sm: 0.2rem;
$btn-padding-x-sm: 0.5rem;
$card-bg: $body-bg;
$link-decoration: none !default;
$link-hover-decoration: underline !default;
$border-radius: .4rem !default;
$border-radius-lg: .5rem !default;
$badge-border-radius: 0.2rem;
$tooltip-max-width: 350px;
$tooltip-padding-y: 0.4rem !default;
$tooltip-padding-x: 0.8rem !default;
$navbar-padding-y: 1rem;
$navbar-padding-x: 1rem;
$navbar-brand-font-size: 1.5rem;
code {
background-color: $body-bg !important;
}
.fontawesome-shadow {
filter: drop-shadow(1px 1px 1px #666);
}
.fontawesome-shadow-dark {
filter: drop-shadow(1px 1px 1px #262626);
}
.text-twitter {
color: #1d9bf0;
}
.mb-large {
margin-bottom: 100px;
}
.hljs, .hljs-subst {
color: $body-color;
}
.text-small {
font-size: 80%;
}
.text-tiny {
font-size: 75% !important;
}
.text-60pct {
font-size: 50% !important;
}
.text-50pct {
font-size: 50% !important;
}
.border-dotted {
border-bottom: 1px dotted #ccc;
}
.border-thick {
border-width: 4px !important;
}
body {
/*font-size: 14px;*/
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
/*font: 14px 'Open Sans', "Lucida Grande", Helvetica, Arial, sans-serif;*/
}
hr {
margin: 5px 0 15px 0;
}
img.header-image {
margin-top: -10px;
margin-bottom: -5px;
width: 40px;
height: 40px;
margin-right: 10px;
}
$font-family-monospace: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
.font-monospace {
/*font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji !important;*/
/*font-family: "Source Code Pro", monospace !important;*/
}
code, .font-json, .font-data, .font-plaintext {
font-family: "Source Code Pro", monospace !important;
}
.hr-thick {
height: 3px !important;
}
.mb-section {
margin-bottom: 1.5rem !important;
}
.mb-tiny {
margin-bottom: 3px;
}
.mb-huge {
margin-bottom: 200px;
}
.user-message-markdown p {
margin-bottom: 0 !important;
}
/*.nav-tabs .nav-link.active {
background-color: transparent;
border-color: #dee2e6 #dee2e6 #f8f9fa;
}*/
pre {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
margin-bottom: 0 !important;
}
dd {
margin-bottom: 1rem !important;
}
.word-wrap {
word-wrap: break-word;
word-break: break-all;
}
.table th {
border-top: none;
}
.tx-io-label, .tx-io-content, .tx-io-desc, .tx-io-value {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 0px;
padding-left: 0px;
margin-bottom: 2px;
float: left;
}
@media (min-width: 576px) {
.tx-io-label { max-width: 100%; }
.tx-io-content { max-width: 100%; }
.tx-io-desc { max-width: 100%; }
.tx-io-value { max-width: 100%; }
}
@media (min-width: 768px) {
.tx-io-label { max-width: 8%; }
.tx-io-content { max-width: 92%; }
.tx-io-desc { max-width: 65%; }
.tx-io-value { max-width: 35%; text-align: right; }
}
@media (min-width: 992px) {
.tx-io-label { max-width: 11%; }
.tx-io-content { max-width: 89%; }
.tx-io-desc { max-width: 60%; }
.tx-io-value { max-width: 40%; text-align: right; }
}
@media (min-width: 1200px) {
.tx-io-label { max-width: 11%; }
.tx-io-content { max-width: 89%; }
.tx-io-desc { max-width: 69%; }
.tx-io-value { max-width: 31%; text-align: right; }
}
@media (min-width: 1920px) {
.tx-io-label { max-width: 7.5%; }
.tx-io-content { max-width: 92.5%; }
.tx-io-desc { max-width: 70%; }
.tx-io-value { max-width: 30%; text-align: right; }
}
.footer-link {
color: #ddd;
text-decoration: underline;
}
.footer-link:hover {
color: white;
}
.hljs-type, .hljs-string, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion {
color: #3aa54f;
}
.hljs-literal, .hljs-number {
color: #0dcaf0;
}

6
public/style/dark.css Normal file

File diff suppressed because one or more lines are too long

1
public/style/highlight.min.css vendored Normal file
View file

@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:.5em;background:#f0f0f0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#78a960}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

6
public/style/light.css Normal file

File diff suppressed because one or more lines are too long

View file

@ -109,6 +109,17 @@ router.get("/stats", function(req, res, next) {
});
router.get('/resetUserSettings', (req, res) => {
req.session.userSettings = {};
var userSettings = {};
res.cookie("user-settings", JSON.stringify(userSettings));
res.redirect(req.headers.referer);
});
router.get('/heapdump', (req, res) => {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

View file

@ -27,7 +27,7 @@ const rpcApi = require("./../app/api/rpcApi.js");
const forceCsrf = csurf({ ignoreMethods: [] });
var noTxIndexMsg = "\n\nYour node does not have `txindex` enabled. Without it, you can only lookup wallet, mempool, and recently confirmed transactions by their `txid`. Searching for non-wallet transactions that were confirmed more than "+config.noTxIndexSearchDepth+" blocks ago is only possible if you provide the confirmed block height in addition to the txid, using `<txid>@<height>` in the search box.";
var noTxIndexMsg = "\n\nYour node does not have **txindex** enabled. Without it, you can only lookup wallet, mempool, and recently confirmed transactions by their **txid**. Searching for non-wallet transactions that were confirmed more than "+config.noTxIndexSearchDepth+" blocks ago is only possible if the confirmed block height is available.";
router.get("/", asyncHandler(async (req, res, next) => {
try {
@ -101,13 +101,13 @@ router.get("/", asyncHandler(async (req, res, next) => {
}));
promises.push(new Promise(async (resolve, reject) => {
res.locals.hashrate1d = await utils.timePromise("promises.index.getNetworkHashrate-144", coreApi.getNetworkHashrate(144));
res.locals.hashrate7d = await utils.timePromise("promises.index.getNetworkHashrate-1008", coreApi.getNetworkHashrate(1008));
resolve();
}));
promises.push(new Promise(async (resolve, reject) => {
res.locals.hashrate7d = await utils.timePromise("promises.index.getNetworkHashrate-1008", coreApi.getNetworkHashrate(1008));
res.locals.hashrate30d = await utils.timePromise("promises.index.getNetworkHashrate-4320", coreApi.getNetworkHashrate(4320));
resolve();
}));
@ -163,19 +163,19 @@ router.get("/", asyncHandler(async (req, res, next) => {
resolve();
}));
if (getblockchaininfo.chain !== 'regtest') {
var targetBlocksPerDay = 24 * 60 * 60 / global.coinConfig.targetBlockTimeSeconds;
var targetBlocksPerDay = 24 * 60 * 60 / global.coinConfig.targetBlockTimeSeconds;
res.locals.targetBlocksPerDay = targetBlocksPerDay;
promises.push(new Promise(async (resolve, reject) => {
if (getblockchaininfo.chain !== 'regtest') {
/*promises.push(new Promise(async (resolve, reject) => {
res.locals.txStats = await utils.timePromise("promises.index.getTxCountStats", coreApi.getTxCountStats(targetBlocksPerDay / 4, -targetBlocksPerDay, "latest"));
resolve();
}));
}));*/
var chainTxStatsIntervals = [ [targetBlocksPerDay, "24 hours"], [targetBlocksPerDay * 7, "1 week"], [targetBlocksPerDay * 30, "1 month"], [targetBlocksPerDay * 365, "1 year"] ]
var chainTxStatsIntervals = [ [targetBlocksPerDay, "24 hours"], [7 * targetBlocksPerDay, "7 days"], [30 * targetBlocksPerDay, "30 days"] ]
.filter(dat => dat[0] <= getblockchaininfo.blocks);
// promiseResults[8-X] (if not regtest)
res.locals.chainTxStats = {};
for (var i = 0; i < chainTxStatsIntervals.length; i++) {
promises.push(new Promise(async (resolve, reject) => {
@ -195,7 +195,83 @@ router.get("/", asyncHandler(async (req, res, next) => {
}));
}
/*promises.push(new Promise(async (resolve, reject) => {
global.rpcClient.command('getblocktemplate', {"rules": ["segwit"]}, function(err, result, resHeaders) {
if (err) {
return reject(err);
}
res.locals.nextBlockTemplate = result;
var minFeeRate = 1000000;
var maxFeeRate = 0;
var minFeeTxid = null;
var maxFeeTxid = null;
result.transactions.forEach(tx => {
var feeRate = tx.fee / tx.weight * 4;
if (feeRate < minFeeRate) {
minFeeRate = feeRate;
minFeeTxid = tx.txid;
}
if (feeRate > maxFeeRate) {
maxFeeRate = feeRate;
maxFeeTxid = tx.txid;
}
});
res.locals.nextBlockMinFeeRate = minFeeRate;
res.locals.nextBlockMaxFeeRate = maxFeeRate;
res.locals.nextBlockMinFeeTxid = minFeeTxid;
res.locals.nextBlockMaxFeeTxid = maxFeeTxid;
resolve();
});
}));*/
await Promise.all(promises);
var firstBlockHeader = res.locals.difficultyPeriodFirstBlockHeader;
var currentBlock = res.locals.latestBlocks[0];
var heightDiff = currentBlock.height - firstBlockHeader.height;
var timeDiff = currentBlock.mediantime - firstBlockHeader.mediantime;
var timePerBlock = timeDiff / heightDiff;
var timePerBlockDuration = moment.duration(timePerBlock * 1000);
var daysUntilAdjustment = new Decimal(res.locals.blocksUntilDifficultyAdjustment).times(timePerBlock).dividedBy(60 * 60 * 24);
var duaDP1 = daysUntilAdjustment.toDP(1);
var daysUntilAdjustmentStr = daysUntilAdjustment > 1 ? `~${duaDP1} day${duaDP1 == "1" ? "" : "s"}` : "< 1 day";
if (timePerBlock > 600) {
var diffAdjPercent = new Decimal(timeDiff / heightDiff / 600).times(100).minus(100);
var diffAdjText = `Blocks during the current difficulty epoch have taken this long, on average, to be mined. If this pace continues, then in ${res.locals.blocksUntilDifficultyAdjustment.toLocaleString()} block${res.locals.blocksUntilDifficultyAdjustment == 1 ? "" : "s"} (${daysUntilAdjustmentStr}) the difficulty will adjust downward: -${diffAdjPercent.toDP(1)}%`;
var diffAdjSign = "-";
var textColorClass = "text-danger";
} else {
var diffAdjPercent = new Decimal(100).minus(new Decimal(timeDiff / heightDiff / 600).times(100));
var diffAdjText = `Blocks during the current difficulty epoch have taken this long, on average, to be mined. If this pace continues, then in ${res.locals.blocksUntilDifficultyAdjustment.toLocaleString()} block${res.locals.blocksUntilDifficultyAdjustment == 1 ? "" : "s"} (${daysUntilAdjustmentStr}) the difficulty will adjust upward: +${diffAdjPercent.toDP(1)}%`;
var diffAdjSign = "+";
var textColorClass = "text-success";
}
res.locals.difficultyAdjustmentData = {
estimateAvailable: !isNaN(diffAdjPercent),
blocksLeft: res.locals.blocksUntilDifficultyAdjustment,
daysLeftStr: daysUntilAdjustmentStr,
calculationBlockCount: heightDiff,
currentEpoch: res.locals.difficultyPeriod,
delta: diffAdjPercent,
sign: diffAdjSign
//nameDesc: `Estimate for the difficulty adjustment that will occur in ${res.locals.blocksUntilDifficultyAdjustment.toLocaleString()} block${res.locals.blocksUntilDifficultyAdjustment == 1 ? "" : "s"} (${daysUntilAdjustmentStr}). This is calculated using the average block time over the last ${heightDiff} block(s). This estimate becomes more reliable as the difficulty epoch nears its end.`,
};
res.render("index");
@ -270,7 +346,7 @@ router.get("/mempool-summary", asyncHandler(async (req, res, next) => {
}));
promises.push(new Promise(async (resolve, reject) => {
const mempooltxids = await utils.timePromise("promises.mempool-summary.getMempoolTxids", coreApi.getMempoolTxids());
const mempooltxids = await utils.timePromise("promises.mempool-summary.getAllMempoolTxids", coreApi.getAllMempoolTxids());
var debugMaxCount = 0;
@ -404,9 +480,16 @@ router.get("/disconnect", function(req, res, next) {
router.get("/changeSetting", function(req, res, next) {
if (req.query.name) {
req.session[req.query.name] = req.query.value;
if (!req.session.userSettings) {
req.session.userSettings = {};
}
res.cookie('user-setting-' + req.query.name, req.query.value);
req.session.userSettings[req.query.name] = req.query.value;
var userSettings = JSON.parse(req.cookies["user-settings"] || "{}");
userSettings[req.query.name] = req.query.value;
res.cookie("user-settings", JSON.stringify(userSettings));
}
res.redirect(req.headers.referer);
@ -878,6 +961,10 @@ router.get("/tx/:transactionId", asyncHandler(async (req, res, next) => {
var tx = rawTxResult.transactions[0];
res.locals.tx = tx;
res.locals.isCoinbaseTx = tx.vin[0].coinbase;
res.locals.result.getrawtransaction = tx;
res.locals.result.txInputs = rawTxResult.txInputsByTransaction[txid] || {};
@ -917,12 +1004,22 @@ router.get("/tx/:transactionId", asyncHandler(async (req, res, next) => {
next();
} catch (err) {
res.locals.userMessageMarkdown = `Failed to load transaction: txid=**${txid}**`;
if (global.prunedBlockchain && res.locals.blockHeight && res.locals.blockHeight < global.pruneHeight) {
// Failure to load tx here is expected and a full description of the situation is given to the user
// in the UI. No need to also show an error userMessage here.
if (!global.txindexAvailable) {
} else if (!global.txindexAvailable) {
res.locals.noTxIndexMsg = noTxIndexMsg;
// As above, failure to load the tx is expected here and good user feedback is given in the UI.
// No need for error userMessage.
} else {
res.locals.userMessageMarkdown = `Failed to load transaction: txid=**${txid}**`;
}
utils.logError("1237y4ewssgt", err);
res.render("transaction");
@ -1048,7 +1145,7 @@ router.get("/address/:address", function(req, res, next) {
res.locals.txids = txids;
(global.txindexAvailable
? coreApi.getRawTransactionsWithInputs(txids)
? coreApi.getRawTransactionsWithInputs(txids, 5)
: coreApi.getRawTransactionsByHeights(txids, blockHeightsByTxid)
.then(transactions => ({ transactions, txInputsByTransaction: {} }))
).then(function(rawTxResult) {
@ -1471,43 +1568,76 @@ router.post("/terminal", function(req, res, next) {
}
});
router.get("/unconfirmed-tx", function(req, res, next) {
var limit = config.site.browseBlocksPageSize;
var offset = 0;
var sort = "desc";
router.get("/mempool-tx", asyncHandler(async (req, res, next) => {
try {
var limit = config.site.browseBlocksPageSize;
var offset = 0;
var sort = "desc";
if (req.query.limit) {
limit = parseInt(req.query.limit);
}
if (req.query.limit) {
limit = parseInt(req.query.limit);
}
if (req.query.offset) {
offset = parseInt(req.query.offset);
}
if (req.query.offset) {
offset = parseInt(req.query.offset);
}
if (req.query.sort) {
sort = req.query.sort;
}
if (req.query.sort) {
sort = req.query.sort;
}
res.locals.limit = limit;
res.locals.offset = offset;
res.locals.sort = sort;
res.locals.paginationBaseUrl = "./unconfirmed-tx";
res.locals.limit = limit;
res.locals.offset = offset;
res.locals.sort = sort;
res.locals.paginationBaseUrl = "./mempool-tx";
coreApi.getMempoolDetails(offset, limit).then(function(mempoolDetails) {
res.locals.mempoolDetails = mempoolDetails;
const mempoolData = await utils.timePromise("promises.mempool-tx.getMempoolTxids", coreApi.getMempoolTxids(limit, offset));
res.render("unconfirmed-transactions");
const txids = mempoolData.txids;
res.locals.txCount = mempoolData.txCount;
const promises = [];
promises.push(new Promise(async (resolve, reject) => {
const transactionData = await utils.timePromise("promises.mempool-tx.getRawTransactionsWithInputs", coreApi.getRawTransactionsWithInputs(txids, 5));
res.locals.transactions = transactionData.transactions;
res.locals.txInputsByTransaction = transactionData.txInputsByTransaction;
resolve();
}));
res.locals.mempoolDetailsByTxid = {};
txids.forEach(txid => {
promises.push(new Promise(async (resolve, reject) => {
const mempoolTxidDetails = await utils.timePromise("promises.mempool-tx.getMempoolTxDetails", coreApi.getMempoolTxDetails(txid, false));
res.locals.mempoolDetailsByTxid[txid] = mempoolTxidDetails;
resolve();
}));
});
await Promise.all(promises);
res.render("mempool-tx");
next();
}).catch(function(err) {
res.locals.userMessage = "Error: " + err;
} catch (err) {
utils.logError("3297gfsdyde3q", err);
res.locals.userMessage = "Error building page: " + err;
res.render("unconfirmed-transactions");
res.render("mempool-tx");
next();
});
});
}
}));
router.get("/tx-stats", function(req, res, next) {
var dataPoints = 100;

View file

@ -11,73 +11,55 @@ block content
span.font-monospace #{address}
else
h1.h3 Address
br
small.font-monospace #{address}
.clearfix
.float-start
+pageTitle("Address", address, true)
.float-start.float-md-end.mb-3.mb-md-0
img(src=addressQrCodeUrl, alt=address, style="border: solid 1px #ccc;")
hr
if (payoutAddressForMiner)
div.alert.alert-primary.shadow-sm.pb-0
div.float-start(style="width: 50px; height: 50px; font-size: 18px;")
i.fas.fa-certificate.fa-2x(style="margin-top: 10px;")
h4.alert-heading.h6.fw-bold #{coinConfig.name} Fun
p
span This looks like a miner payout address for
span.fw-bold #{payoutAddressForMiner.name}
if (coinConfig.miningPoolsConfigUrls.length > 1)
span (see the configurations:
each item, index in coinConfig.miningPoolsConfigUrls
if (index > 0)
span ,
a(href=item) config ##{index + 1}
span )
else
span (see the configuration
a(href=coinConfig.miningPoolsConfigUrls[0]) here
span )
else if (global.specialAddresses[address] && global.specialAddresses[address].type == "fun")
div.alert.alert-primary.shadow-sm.pb-0
div.float-start(style="width: 50px; height: 50px; font-size: 18px;")
i.fas.fa-certificate.fa-2x(style="margin-top: 10px;")
h4.alert-heading.h6.fw-bold #{coinConfig.name} Fun
// special address info
- var saInfo = global.specialAddresses[address].addressInfo;
if (saInfo.alertBodyHtml)
p
span !{saInfo.alertBodyHtml}
if (saInfo.referenceUrl && saInfo.referenceUrl.trim().length > 0 && saInfo.alertBodyHtml.indexOf(saInfo.referenceUrl) == -1)
span
a(href=saInfo.referenceUrl) Read more
else
p
span #{saInfo.summary}
if (saInfo.referenceUrl && saInfo.referenceUrl.trim().length > 0)
span
a(href=saInfo.referenceUrl) Read more
if (false)
pre
code.json.bg-light #{JSON.stringify(addressObj, null, 4)}
ul.nav.nav-tabs.mb-3
li.nav-item
a.nav-link.active(data-bs-toggle="tab", href="#tab-details", role="tab") Details
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json", role="tab") JSON
code.json #{JSON.stringify(addressObj, null, 4)}
div.tab-content
div.tab-pane.active(id="tab-details", role="tabpanel")
+pageTabs(["Details", "JSON"])
.tab-content
+pageTab("Details", true)
if (payoutAddressForMiner)
+funAlert
span This looks like a miner payout address for
span.fw-bold #{payoutAddressForMiner.name}
if (coinConfig.miningPoolsConfigUrls.length > 1)
span (see the miner-identifications configurations:
each item, index in coinConfig.miningPoolsConfigUrls
if (index > 0)
span ,
a(href=item) config ##{index + 1}
span )
else
span (see the configuration
a(href=coinConfig.miningPoolsConfigUrls[0]) here
span )
else if (global.specialAddresses[address] && global.specialAddresses[address].type == "fun")
- var saInfo = global.specialAddresses[address].addressInfo;
+funAlert(saInfo.alertBodyHtml, saInfo.summary, saInfo.referenceUrl)
.clearfix
.float-end
if (`${userSettings["addressPageShowTechDetails"]}` == "false")
a.btn.btn-primary.btn-sm.me-2(href=`./changeSetting?name=addressPageShowTechDetails&value=true`, title="Display technical details for this address.", data-bs-toggle="tooltip")
i.far.fa-plus-square.me-2
| Technical Details
if (false && config.electrumXServers && config.electrumXServers.length > 0)
if (session.hideElectrumTrustWarnings != "true")
@ -87,219 +69,156 @@ block content
a.close(href="./changeSetting?name=hideElectrumTrustWarnings&value=true", aria-label="Close", style="text-decoration: none;")
span(aria-hidden="true") &times;
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Summary
- var x = result.validateaddress;
- var flags = [];
- if (x.ismine) { flags.push(["Mine", "This address is owned by your node."]); }
- if (x.iswatchonly) { flags.push(["Watch-Only"]); }
if (addressDetails || flags.length > 0)
.clearfix
.float-start
+sectionTitle("Summary")
.float-start
if (config.addressApi)
small.text-muted.border-dotted.ms-2(title=`Some details for this address were queried from ${config.addressApi}` data-bs-toggle="tooltip") Trust Note
hr
+contentSection
div.row
div.col-md-6
+summaryRow((addressDetails ? ((addressDetails.txCount != null ? 1 : 0) + (addressDetails.balanceSat ? 1 : 0) + (addressDetails.unconfirmedBalanceSat ? 1 : 0) + (addressDetails.totalReceivedSat ? 1 : 0)) : 0) + (flags.length > 0 ? 1 : 0))
if (addressDetails && addressDetails.txCount != null)
+summaryItem("Transactions")
| #{addressDetails.txCount.toLocaleString()}
if (addressObj.hash)
div.row
div.summary-split-table-label Hash 160
div.summary-split-table-content.font-monospace #{addressObj.hash.toString("hex")}
if (addressDetails && addressDetails.balanceSat)
+summaryItem("Balance")
span(class=(addressDetails.balanceSat > 0 ? "text-success" : false))
- var currencyValue = new Decimal(addressDetails.balanceSat).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include includes/value-display.pug
if (result.validateaddress.scriptPubKey)
div.row
div.summary-split-table-label Script Public Key
div.summary-split-table-content.font-monospace #{result.validateaddress.scriptPubKey}
if (addressObj.hasOwnProperty("version"))
div.row
div.summary-split-table-label Version
div.summary-split-table-content.font-monospace #{addressObj.version}
if (result.validateaddress.hasOwnProperty("witness_version"))
div.row
div.summary-split-table-label Witness Version
div.summary-split-table-content.font-monospace #{result.validateaddress.witness_version}
if (result.validateaddress.witness_program)
div.row
div.summary-split-table-label Witness Program
div.summary-split-table-content.font-monospace #{result.validateaddress.witness_program}
if (firstSeenTransaction && firstSeenTransaction.confirmations > 0)
div.row
div.summary-split-table-label First Seen
div.summary-split-table-content.font-monospace
if (getblockchaininfo)
span Block ##{(getblockchaininfo.blocks - firstSeenTransaction.confirmations).toLocaleString()}
else
a(href=`./block/${firstSeenTransaction.blockhash}`) #{firstSeenTransaction.blockhash}
br
span #{moment.utc(new Date(firstSeenTransaction.time * 1000)).format("Y-MM-DD HH:mm:ss")} utc
- var timeAgoTime = firstSeenTransaction.time;
include includes/time-ago.pug
if (false)
pre
code.json.bg-light #{JSON.stringify(firstSeenTransaction)}
if (balance)
if (balance.conflictedResults)
div.row
div.summary-split-table-label Balance
div.summary-split-table-content.font-monospace
span.text-warning Conflicted ElectrumX Results
each item in balance.conflictedResults
- var currencyValue = item.confirmed / coinConfig.baseCurrencyUnit.multiplier;
include includes/value-display.pug
else
div.row
div.summary-split-table-label Balance
div.summary-split-table-content.font-monospace
- var currencyValue = balance.result.confirmed / coinConfig.baseCurrencyUnit.multiplier;
include includes/value-display.pug
if (balance.unconfirmed)
div.row
div.summary-split-table-label Unconfirmed
div.summary-split-table-content.font-monospace
- var currencyValue = balance.unconfirmed / coinConfig.baseCurrencyUnit.multiplier;
include includes/value-display.pug
if (addressDetails && addressDetails.balanceSat)
div.row
div.summary-split-table-label Balance
div.summary-split-table-content.font-monospace
- var currencyValue = new Decimal(addressDetails.balanceSat).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include includes/value-display.pug
if (addressDetails && addressDetails.unconfirmedBalanceSat)
div.row
div.summary-split-table-label Pending Balance
div.summary-split-table-content.font-monospace
- var currencyValue = new Decimal(addressDetails.unconfirmedBalanceSat).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include includes/value-display.pug
if (addressDetails && addressDetails.txCount)
div.row
div.summary-split-table-label Transactions
div.summary-split-table-content.font-monospace #{addressDetails.txCount.toLocaleString()}
div.row
div.summary-split-table-label QR Code
div.summary-split-table-content.font-monospace
img(src=addressQrCodeUrl, alt=address, style="border: solid 1px #ccc;")
div.col-md-6
if (electrumScripthash)
div.row
div.summary-split-table-label Scripthash
div.summary-split-table-content.font-monospace #{electrumScripthash}
- var x = result.validateaddress;
- var flagNames = ["Is Valid?", "Is Script?", "Is Witness?", "Is Mine?", "Is Watch-Only?"];
- var flags = [x.isvalid, x.isscript, x.iswitness, x.ismine, x.iswatchonly];
each flagName, index in flagNames
div.row
div.summary-split-table-label #{flagName}
div.summary-split-table-content.font-monospace
if (flags[index])
i.fas.fa-check.text-success
else
i.fas.fa-times.text-warning
if (false)
div.card.shadow-sm.mb-3
div.card-header
span.h6 Flags
div.card-body
div.table-responsive
table.table.text-center
thead
tr
th Is Valid?
th Is Script?
th Is Witness?
th Is Mine?
th Is Watch-Only?
tbody
tr
- var x = result.validateaddress;
- var flags = [x.isvalid, x.isscript, x.iswitness, x.ismine, x.iswatchonly];
each flag in flags
td
if (flag)
i.fas.fa-check.text-success
else
i.fas.fa-times.text-warning
div.card.shadow-sm.mb-3
div.card-body
div.clearfix
div.float-start
h3.h6.mb-0
if (addressDetails && addressDetails.txCount)
if (addressDetails.txCount == 1)
span 1 Transaction
if (addressDetails && addressDetails.unconfirmedBalanceSat)
+summaryItem("Unconfirmed Balance")
span(class=(addressDetails.unconfirmedBalanceSat > 0 ? "text-success" : "text-danger"))
if (addressDetails.unconfirmedBalanceSat > 0)
span +
else
span #{addressDetails.txCount.toLocaleString()} Transactions
span -
- var currencyValue = new Decimal(Math.abs(addressDetails.unconfirmedBalanceSat)).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include includes/value-display.pug
if (addressDetails && addressDetails.totalReceivedSat)
+summaryItem("Total Received / Sent")
span(class=(addressDetails.totalReceivedSat > 0 ? "text-success" : false))
if (addressDetails.totalReceivedSat)
span +
- var currencyValue = new Decimal(Math.abs(addressDetails.totalReceivedSat)).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include includes/value-display.pug
br
span(class=(addressDetails.totalSentSat > 0 ? "text-danger" : false))
if (addressDetails.totalSentSat > 0)
span -
- var currencyValue = new Decimal(Math.abs(addressDetails.totalSentSat)).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include includes/value-display.pug
if (flags.length > 0)
+summaryItem("Flags")
each flagItem, flagIndex in flags
span.badge.bg-primary(class=(flagIndex > 0 ? "ms-1" : false), title=(flagItem.length > 1 ? flagItem[1] : false), data-bs-toggle=(flagItem.length > 1 ? "tooltip" : false)) #{flagItem[0]}
+contentSection("Technical Details", true, "addressPageShowTechDetails")
+summaryRow(1 + (addressObj.hash ? 1 : 0) + (addressObj.hasOwnProperty("version") ? 1 : 0) + (result.validateaddress.hasOwnProperty("witness_version") ? 1 : 0) + (result.validateaddress.witness_program ? 1 : 0) + (electrumScripthash ? 1 : 0))
if (addressObj.hasOwnProperty("version"))
+summaryItem("Version")
| #{addressObj.version}
if (addressObj.hash)
+summaryItem("Hash 160")
| #{utils.ellipsizeMiddle(addressObj.hash.toString("hex"), 12)}
+copyTextButton(addressObj.hash.toString("hex"))
+summaryItem("Script Pub Key")
| #{utils.ellipsizeMiddle(result.validateaddress.scriptPubKey, 12)}
+copyTextButton(result.validateaddress.scriptPubKey)
if (result.validateaddress.hasOwnProperty("witness_version"))
+summaryItem("Witness Version")
| #{result.validateaddress.witness_version}
if (result.validateaddress.witness_program)
+summaryItem("Witness Program")
| #{utils.ellipsizeMiddle(result.validateaddress.witness_program, 12)}
+copyTextButton(result.validateaddress.witness_program)
if (electrumScripthash)
+summaryItem("Script Hash", "Electrum-lookup script hash")
| #{utils.ellipsizeMiddle(electrumScripthash, 12)}
+copyTextButton(electrumScripthash)
.clearfix
.float-start
+sectionTitle(addressDetails && addressDetails.txCount ? `${addressDetails.txCount.toLocaleString()} Transaction${addressDetails.txCount == 1 ? "" : "s"}` : "Transactions")
.float-start
if (config.addressApi)
if (config.addressApi == "electrumx")
small.text-muted.border-dotted.ms-2(title=`The list of transaction IDs for this address was queried from ElectrumX (using the configured server(s))` data-bs-toggle="tooltip") Trust Note
else
small.text-muted.border-dotted.ms-2(title=`The list of transaction IDs for this address was queried from ${config.addressApi}` data-bs-toggle="tooltip") Trust Note
.float-end
if (!crawlerBot && txids && txids.length > 1 && addressApiSupport.sortDesc && addressApiSupport.sortAsc)
div.float-end
a.pull-right.dropdown-toggle(href="#", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
if (sort == "desc")
span Newest First
else
span Transactions
span Oldest First
if (config.addressApi)
if (config.addressApi == "electrumx")
small.text-muted.border-dotted.ms-2(title=`The list of transaction IDs for this address was queried from ElectrumX (using the configured server(s))` data-bs-toggle="tooltip") Trust Note
else
small.text-muted.border-dotted.ms-2(title=`The list of transaction IDs for this address was queried from ${config.addressApi}` data-bs-toggle="tooltip") Trust Note
if (!crawlerBot && txids && txids.length > 1 && addressApiSupport.sortDesc && addressApiSupport.sortAsc)
div.float-end
a.pull-right.dropdown-toggle(href="#", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
div.dropdown-menu.dropdown-menu-end
a.dropdown-item(href=`./address/${address}`)
if (sort == "desc")
span Newest First
else
span Oldest First
i.fa.fa-check
span Newest First
a.dropdown-item(href=`./address/${address}?sort=asc`)
if (sort != "desc")
i.fa.fa-check
span Oldest First
div.dropdown-menu.dropdown-menu-end
a.dropdown-item(href=`./address/${address}`)
if (sort == "desc")
i.fa.fa-check
span Newest First
a.dropdown-item(href=`./address/${address}?sort=asc`)
if (sort != "desc")
i.fa.fa-check
span Oldest First
else if (txids && txids.length > 1 && addressApiSupport.sortDesc && !addressApiSupport.sortAsc)
div.float-end
span.text-muted Newest First
else if (txids && txids.length > 1 && addressApiSupport.sortDesc && !addressApiSupport.sortAsc)
div.float-end
span.text-muted Newest First
hr
if (conflictedTxidResults)
div.alert.alert-warning.pb-0
div.float-start(style="width: 55px; height: 50px; font-size: 18px;")
i.fas.fa-exclamation-triangle.fa-2x(style="margin-top: 10px;")
h4.alert-heading.h6.fw-bold Trust Warning
p
span The transaction history for this address was requested from mulitple ElectrumX servers and the results did not match. The results below were obtained only from
span.fw-bold #{electrumHistory.server}
+contentSection(null, false, null, true, false)
if (conflictedTxidResults)
div.alert.alert-warning.pb-0
div.float-start(style="width: 55px; height: 50px; font-size: 18px;")
i.fas.fa-exclamation-triangle.fa-2x(style="margin-top: 10px;")
h4.alert-heading.h6.fw-bold Trust Warning
p
span The transaction history for this address was requested from mulitple ElectrumX servers and the results did not match. The results below were obtained only from
span.fw-bold #{electrumHistory.server}
if (true)
if (addressApiError && addressApiError.error && addressApiError.error.code && addressApiError.error.code == -32600)
span Failed to retrieve transaction history from ElectrumX. See
a(href="https://github.com/janoside/btc-rpc-explorer/issues/67") Issue #67
span for more information.
if (true)
if (addressApiError && addressApiError.error && addressApiError.error.code && addressApiError.error.code == -32600)
span Failed to retrieve transaction history from ElectrumX. See
a(href="https://github.com/janoside/btc-rpc-explorer/issues/67") Issue #67
span for more information.
else if (addressApiError && addressApiError.userText)
else if (addressApiError && addressApiError.userText)
+contentSection
div.text-danger Error: #{addressApiError.userText}
else if (addressDetailsErrors && addressDetailsErrors.length > 0)
else if (addressDetailsErrors && addressDetailsErrors.length > 0)
+contentSection
each err, errIndex in addressDetailsErrors
if (err.e && err.e && err.e.message == "history too large")
span Failed to retrieve transaction history from ElectrumX because this address is involved in too many transactions (more than ElectrumX currently supports retrieving). See
@ -307,16 +226,18 @@ block content
span for more information.
else if (err == "No address API configured")
span No address API is configured. See
a(href="https://github.com/janoside/btc-rpc-explorer/blob/master/.env-sample") the example configuration file
span for help setting up an address API if desired.
.mb-2 No address API is configured.
.mb-0 See
| <a href="https://github.com/janoside/btc-rpc-explorer/blob/master/.env-sample">the example configuration file</a>
| for help setting up an address API.
else
span.text-danger Error retrieving transaction history (##{errIndex + 1})
pre
code.json #{JSON.stringify(err, null, 4)}
else if (!global.txindexAvailable && transactions.length == 0)
else if (!global.txindexAvailable && transactions.length == 0)
+contentSection
table.table.table-striped
each txid, txidIndex in txids
tr
@ -327,116 +248,29 @@ block content
- var blockHeightParam = blockHeight > 0 ? `@${blockHeight}` : "";
a(href=`./tx/${txid}${blockHeightParam}`) #{txid}
else if (transactions.length == 0)
else if (transactions.length == 0)
+contentSection
span No transactions found
each tx, txIndex in transactions
//pre
// code.json.bg-light #{JSON.stringify(tx, null, 4)}
div.card.shadow-sm(class=((txIndex < (transactions.length - 1) || txids.length > limit) ? "mb-3" : ""))
div.card-header.font-monospace.clearfix
div.float-start.me-0
if (sort == "desc")
span ##{(addressDetails.txCount - offset - txIndex).toLocaleString()}
else
span ##{(offset + txIndex + 1).toLocaleString()}
span &ndash;
div.row
div.col-md-8
if (tx && tx.txid)
- var blockHeight = addressDetails.blockHeightsByTxid[tx.txid];
- var blockHeightParam = !global.txindexAvailable && blockHeight > 0 ? `@${blockHeight}` : "";
a(href=`./tx/${tx.txid}${blockHeightParam}`) #{tx.txid}
if (global.specialTransactions && global.specialTransactions[tx.txid])
span
a(data-bs-toggle="tooltip", title=`${coinConfig.name} Fun! See transaction for details`)
i.fas.fa-certificate.text-primary
br
if (addrGainsByTx[tx.txid])
- var currencyValue = addrGainsByTx[tx.txid];
span.text-success +
include includes/value-display.pug
if (addrLossesByTx[tx.txid])
span /
if (addrLossesByTx[tx.txid])
- var currencyValue = addrLossesByTx[tx.txid];
span.text-danger -
include includes/value-display.pug
div.col-md-4
div.text-md-end
if (tx.time)
- var timestampHuman = tx.time;
include includes/timestamp-human.pug
br
- var timeAgoTime = tx.time;
small.text-muted (
include includes/time-ago-text.pug
span ago)
else
span.text-danger Unconfirmed
div.card-body
if (true)
- var txInputs = txInputsByTransaction[tx.txid];
- var blockHeight = blockHeightsByTxid[tx.txid];
- var txIOHighlightAddress = address;
include includes/transaction-io-details.pug
else
p Since this explorer is database-free, it doesn't natively support address transaction history. However, you can configure it to communicate with one or more ElectrumX servers to build and display this data. In doing so, you should be aware that you'll be trusting those ElectrumX servers. If you configure multiple servers the results obtained from each will be cross-referenced against the others. Communicating with ElectrumX servers will also impact your privacy since the servers will know what addresses you're interested in. If these tradeoffs are acceptable, you can see a list of public ElectrumX servers here:
a(href="https://uasf.saltylemon.org/electrum") https://uasf.saltylemon.org/electrum
- var txIOHighlightAddress = address;
- var options = {blockHeightsByTxid: blockHeightsByTxid, showDates: true, highlightAddress: address, addrGainsByTxid: addrGainsByTx, addrLossesByTxid: addrLossesByTx};
+txList(transactions, addressDetails.txCount, limit, offset, txInputsByTransaction, options)
if (false)
pre
code.json.bg-light #{JSON.stringify(transactions, null, 4)}
if (!crawlerBot && addressDetails && addressDetails.txCount > limit)
- var txCount = addressDetails.txCount;
- 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.mt-3
include includes/pagination.pug
div.tab-pane(id="tab-json", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 validateaddress
hr
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(result.validateaddress, null, 4)}
+pageTab("JSON")
+contentSection("validateaddress")
pre
code.json #{JSON.stringify(result.validateaddress, null, 4)}
if (addressDetails)
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 addressDetails
hr
div.highlight
pre
code.json.bg-light #{JSON.stringify(addressDetails, null, 4)}
+contentSection("addressDetails")
pre
code.json #{JSON.stringify(addressDetails, null, 4)}

View file

@ -4,137 +4,169 @@ block headContent
title Admin Dashboard
block content
h1.h3 Admin Dashboard
hr
+pageTitle("Admin Dashboard")
ul.nav.nav-tabs.mb-3
li.nav-item
a.nav-link.active(data-bs-toggle="tab", href="#tab-details", role="tab") Details
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json", role="tab") JSON
+pageTabs(["Details", "JSON"])
div.tab-content
div.tab-pane.active(id="tab-details", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Basics
hr
.tab-content
+pageTab("Details", true)
div.clearfix
div.row
div.summary-table-label Uptime
div.summary-table-content.font-monospace
- var uptime = moment.duration(new Date().getTime() - appStartTime);
span #{uptime.format()}
+contentSection("Tools")
a.btn.btn-primary(href="./admin/resetUserSettings") Reset User Settings
div.row
div.summary-table-label Node Version
div.summary-table-content.font-monospace
span #{global.nodeVersion}
+contentSection("Basics")
+summaryRow(3)
+summaryItem("Uptime")
- var uptime = moment.duration(new Date().getTime() - appStartTime);
| #{uptime.format()}
div.row
div.summary-table-label Node Env
div.summary-table-content.font-monospace
span #{process.env.NODE_ENV || "development"}
+summaryItem("Node Version")
| #{global.nodeVersion}
div.card.shadow-sm.mb-3
div.card-body
h3.h6 App Config
hr
+summaryItem("Node Env")
| #{process.env.NODE_ENV || "development"}
pre
code.json #{JSON.stringify(appConfig, null, 4)}
+contentSection("User Settings")
pre
code.json #{JSON.stringify(userSettings, null, 4)}
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Memory Stats
hr
+contentSection("App Config")
pre
code.json #{JSON.stringify(appConfig, null, 4)}
div.table-responsive
table.table.table-borderless.table-hover
thead
tr
th.text-end Heap Size
th.text-end Used Heap
th.text-end Heap Limit
th.text-end Physical Size
th.text-end Available Size
+contentSection("Memory Stats")
.table-responsive
table.table.table-borderless.table-hover.table-striped
thead
tr
th.text-end Heap Size
th.text-end Used Heap
th.text-end Heap Limit
th.text-end Physical Size
th.text-end Available Size
tbody
tr.text-end.font-monospace
td
- var data = utils.formatLargeNumber(memstats.total_heap_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
tbody
tr.text-end.font-monospace
td
- var data = utils.formatLargeNumber(memstats.total_heap_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.used_heap_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.used_heap_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.heap_size_limit, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.heap_size_limit, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.total_physical_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.total_physical_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.total_available_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
td
- var data = utils.formatLargeNumber(memstats.total_available_size, 2);
span #{data[0]}
small #{data[1].abbreviation}B
if (JSON.stringify(cacheStats) != "{}")
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Cache Stats
hr
+contentSection("Cache Stats")
.table-responsive
table.table.table-borderless.table-hover.table-striped
thead
tr
th Cache
th.text-end Hit
th.text-end Miss
th.text-end Hit Rate
div.table-responsive
table.table.table-borderless.table-hover
thead
tr
th Cache
th.text-end Hit
th.text-end Miss
th.text-end Hit Rate
tbody
each item, itemName in cacheStats
tr.font-monospace
td #{itemName}
td.text-end #{item.hit.toLocaleString()}
td.text-end #{item.miss.toLocaleString()}
td.text-end
if (item.hit > 0 || item.miss > 0)
span #{(100 * item.hit / (item.hit + item.miss)).toLocaleString()}
small %
else
span -
tbody
each item, itemName in cacheStats
tr.font-monospace
td #{itemName}
td.text-end #{item.hit.toLocaleString()}
td.text-end #{item.miss.toLocaleString()}
td.text-end
if (item.hit > 0 || item.miss > 0)
span #{(100 * item.hit / (item.hit + item.miss)).toLocaleString()}
small %
else
span -
+contentSection("Cache Sizes")
pre
code.json #{JSON.stringify(cacheSizes, null, 4)}
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Cache Sizes
hr
+contentSection("RPC Stats")
.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Method
th.text-end Count
th.text-end Time
small (s)
th.text-end Avg Time
small (ms)
th.text-end Successes / Failures
th.text-end Success Rate
pre
code.json #{JSON.stringify(cacheSizes, null, 4)}
tbody
each item, itemName in rpcStats
tr.font-monospace
td #{itemName}
td.text-end #{item.count.toLocaleString()}
td.text-end #{(item.time / 1000).toLocaleString()}
td.text-end #{(item.time / item.count).toLocaleString()}
td.text-end
span.text-success #{item.successes.toLocaleString()}
span.mx-1 /
span.text-danger #{item.failures.toLocaleString()}
div.card.shadow-sm.mb-3
div.card-body
h3.h6 RPC Stats
hr
td.text-end
span #{new Decimal(item.successes).dividedBy(new Decimal(item.successes + item.failures)).times(100).toDP(1)}
small %
div.table-responsive
if (electrumStats)
+contentSection("Electrum Stats")
.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Method
th Action
th.text-end Count
th.text-end First Seen
th.text-end Last Seen
tbody
each item, itemName in electrumStats.base
tr.font-monospace
td #{itemName}
td.text-end #{item.count.toLocaleString()}
if (item.firstSeenAt)
td.text-end #{moment.duration(new Date().getTime() - item.firstSeenAt.getTime()).format()} ago
else
td.text-end -
if (item.lastSeenAt)
td.text-end #{moment.duration(new Date().getTime() - item.lastSeenAt.getTime()).format()} ago
else
td.text-end -
hr
.table-responsive
table.table.table-borderless.table-striped
thead
tr
th RPC
th.text-end Count
th.text-end Time
small (s)
@ -144,7 +176,7 @@ block content
th.text-end Success Rate
tbody
each item, itemName in rpcStats
each item, itemName in electrumStats.rpc
tr.font-monospace
td #{itemName}
td.text-end #{item.count.toLocaleString()}
@ -159,110 +191,38 @@ block content
span #{new Decimal(item.successes).dividedBy(new Decimal(item.successes + item.failures)).times(100).toDP(1)}
small %
if (electrumStats)
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Electrum Stats
hr
div.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Action
th.text-end Count
th.text-end First Seen
th.text-end Last Seen
+contentSection("Error Stats")
if (JSON.stringify(errorStats) == "{}")
span.text-success No errors encountered
tbody
each item, itemName in electrumStats.base
tr.font-monospace
td #{itemName}
td.text-end #{item.count.toLocaleString()}
else
div.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Error ID
th.text-end Count
th.text-end First Seen
th.text-end Last Seen
if (item.firstSeenAt)
td.text-end #{moment.duration(new Date().getTime() - item.firstSeenAt.getTime()).format()} ago
else
td.text-end -
tbody
each item, itemName in errorStats
tr.font-monospace
td #{itemName}
td.text-end #{item.count.toLocaleString()}
td.text-end #{moment.duration(new Date().getTime() - item.firstSeen).format()} ago
td.text-end #{moment.duration(new Date().getTime() - item.lastSeen).format()} ago
if (item.lastSeenAt)
td.text-end #{moment.duration(new Date().getTime() - item.lastSeenAt.getTime()).format()} ago
else
td.text-end -
hr
div.table-responsive
table.table.table-borderless.table-striped
thead
tr
th RPC
th.text-end Count
th.text-end Time
small (s)
th.text-end Avg Time
small (ms)
th.text-end Successes / Failures
th.text-end Success Rate
tbody
each item, itemName in electrumStats.rpc
tr.font-monospace
td #{itemName}
td.text-end #{item.count.toLocaleString()}
td.text-end #{(item.time / 1000).toLocaleString()}
td.text-end #{(item.time / item.count).toLocaleString()}
td.text-end
span.text-success #{item.successes.toLocaleString()}
span.mx-1 /
span.text-danger #{item.failures.toLocaleString()}
td.text-end
span #{new Decimal(item.successes).dividedBy(new Decimal(item.successes + item.failures)).times(100).toDP(1)}
small %
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Error Stats
hr
if (JSON.stringify(errorStats) == "{}")
span.text-success No errors encountered
else
div.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Error ID
th.text-end Count
th.text-end First Seen
th.text-end Last Seen
tbody
each item, itemName in errorStats
tr.font-monospace
td #{itemName}
td.text-end #{item.count.toLocaleString()}
td.text-end #{moment.duration(new Date().getTime() - item.firstSeen).format()} ago
td.text-end #{moment.duration(new Date().getTime() - item.lastSeen).format()} ago
+pageTab("JSON")
+contentSection("Memory Stats")
pre
code.json #{JSON.stringify(memstats, null, 4)}
+contentSection("Error Stats")
pre
code.json #{JSON.stringify(errorStats, null, 4)}
div.tab-pane(id="tab-json", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Memory Stats
hr
div.highlight
pre
code.json #{JSON.stringify(memstats, null, 4)}
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Error Stats
hr
div.highlight
pre
code.json #{JSON.stringify(errorStats, null, 4)}

View file

@ -4,93 +4,82 @@ block headContent
title Stats
block content
h1.h3 Stats
hr
+pageTitle("Admin Stats")
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Performance Stats
hr
+contentSection("Performance Stats")
.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Name
th.text-end Min
th.text-end Avg
th.text-end Max
th.text-end Sum
th.text-end Count
th.text-end First
th.text-end Latest
div.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Name
th.text-end Min
th.text-end Avg
th.text-end Max
th.text-end Sum
th.text-end Count
th.text-end First
th.text-end Latest
tbody
each item, itemIndex in performanceStats
tr.font-monospace
td #{item[0]}
td.text-end #{Math.round(item[1].min).toLocaleString()}
td.text-end #{Math.round(item[1].avg).toLocaleString()}
td.text-end #{Math.round(item[1].max).toLocaleString()}
td.text-end #{Math.round(item[1].sum).toLocaleString()}
td.text-end #{Math.round(item[1].count).toLocaleString()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].firstDate.getTime());
span #{dt.format()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].lastDate.getTime());
span #{dt.format()}
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Event Stats
hr
div.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Name
th.text-end Count
tbody
each item, itemIndex in eventStats
tr.font-monospace
td #{item[0]}
td.text-end #{item[1].toLocaleString()}
div.card.shadow-sm.mb-3
div.card-body
h3.h6 Value Stats
hr
div.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Name
th.text-end Min
th.text-end Avg
th.text-end Max
th.text-end Sum
th.text-end Count
th.text-end First
th.text-end Latest
tbody
each item, itemIndex in valueStats
tr.font-monospace
td #{item[0]}
td.text-end #{Math.round(item[1].min).toLocaleString()}
td.text-end #{Math.round(item[1].avg).toLocaleString()}
td.text-end #{Math.round(item[1].max).toLocaleString()}
td.text-end #{Math.round(item[1].sum).toLocaleString()}
td.text-end #{Math.round(item[1].count).toLocaleString()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].firstDate.getTime());
span #{dt.format()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].lastDate.getTime());
span #{dt.format()}
tbody
each item, itemIndex in performanceStats
tr.font-monospace
td #{item[0]}
td.text-end #{Math.round(item[1].min).toLocaleString()}
td.text-end #{Math.round(item[1].avg).toLocaleString()}
td.text-end #{Math.round(item[1].max).toLocaleString()}
td.text-end #{Math.round(item[1].sum).toLocaleString()}
td.text-end #{Math.round(item[1].count).toLocaleString()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].firstDate.getTime());
span #{dt.format()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].lastDate.getTime());
span #{dt.format()}
+contentSection("Event Stats")
.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Name
th.text-end Count
tbody
each item, itemIndex in eventStats
tr.font-monospace
td #{item[0]}
td.text-end #{item[1].toLocaleString()}
+contentSection("Value Stats")
.table-responsive
table.table.table-borderless.table-striped
thead
tr
th Name
th.text-end Min
th.text-end Avg
th.text-end Max
th.text-end Sum
th.text-end Count
th.text-end First
th.text-end Latest
tbody
each item, itemIndex in valueStats
tr.font-monospace
td #{item[0]}
td.text-end #{Math.round(item[1].min).toLocaleString()}
td.text-end #{Math.round(item[1].avg).toLocaleString()}
td.text-end #{Math.round(item[1].max).toLocaleString()}
td.text-end #{Math.round(item[1].sum).toLocaleString()}
td.text-end #{Math.round(item[1].count).toLocaleString()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].firstDate.getTime());
span #{dt.format()}
td.text-end
- var dt = moment.duration(new Date().getTime() - item[1].lastDate.getTime());
span #{dt.format()}

View file

@ -4,20 +4,15 @@ block headContent
title Bitcoin Whitepaper
block content
div.row
div.col
h1.h3 Bitcoin Whitepaper
hr
+pageTitle("Bitcoin Whitepaper", "(Extracted from the blockchain!)")
p Below is the Bitcoin whitepaper, extracted from data in the Bitcoin blockchain (transaction
a.font-monospace(href="./tx/54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713") 54e48e5f5c6…
| to be precise), served by your Bitcoin Core node.
+dismissableInfoAlert("whitepaperPageNoteDismissed", "About the Bitcoin Whitepaper Tool...")
p Below is the Bitcoin whitepaper, extracted from data in the Bitcoin blockchain (transaction
a.font-monospace(href="./tx/54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713") 54e48e5f5c6…
span.ms-2 to be precise), served by your <b>Bitcoin Core</b> node.
p The data has been decoded by this tool and is displayed in an iframe below. You can view it directly and/or download it at
a(href="./bitcoin.pdf") /bitcoin.pdf
|.
hr
p The data has been decoded by this tool and is displayed in an iframe below. You can view it directly and/or download it at
a(href="./bitcoin.pdf") /bitcoin.pdf
|.
iframe(src="./bitcoin.pdf", style="width: 100%;", height="800")

View file

@ -4,8 +4,7 @@ block headContent
title Block Analysis
block content
h1.h3 Block Analysis
hr
+pageTitle("Block Analysis")
div.card.shadow-sm.mb-huge
div.card-body

View file

@ -4,16 +4,21 @@ block headContent
title Block Analysis ##{result.getblock.height.toLocaleString()}, #{result.getblock.hash}
block content
h1.h3 Block Analysis
small.font-monospace(style="width: 100%;") ##{result.getblock.height.toLocaleString()}
br
small.font-monospace.word-wrap(style="width: 100%;") #{result.getblock.hash}
.mb-3
.clearfix
.float-start
if (result.getblock)
a.btn.btn-sm.btn-primary.ms-2(href=`./block/${result.getblock.hash}`) &laquo; Block Details
.float-end
hr
div.mb-2
if (result.getblock)
a.btn.btn-sm.btn-primary.mb-1(href=`./block/${result.getblock.hash}`) &laquo; Block Details:
span.font-monospace ##{result.getblock.height.toLocaleString()}
+pageTitle("Block Analysis", result.getblock.hash, true)
if (block && (!block.tx || block.tx.length == 0) && global.prunedBlockchain)
div.alert.alert-warning.pb-0(role="alert")
@ -22,263 +27,207 @@ block content
| !{markdown(msgMarkdown)}
else
div.row
- var sumTableLabelClass = (result.blockstats != null ? "summary-split-table-label" : "summary-table-label");
- var sumTableValueClass = (result.blockstats != null ? "summary-split-table-content" : "summary-table-content");
div.mb-3.col
div.card.shadow-sm(style="height: 100%;")
div.card-body.px-2.px-md-3
h3.h6.mb-0 Summary
hr
+contentSection("Summary")
+summaryRow(3 + (result.getblock.weight ? 1 : (result.getblock.size ? 1 : 0)))
+summaryItem("Date")
+timestamp(result.getblock.time, true)
div.clearfix
div.row
div.summary-table-label Date
div.summary-table-content.font-monospace
- var timestampHuman = result.getblock.time;
include includes/timestamp-human.pug
small.ms-1 utc
if (result.getblock.weight)
+summaryItem("Weight", null, "wu", "Weight Units")
| #{result.getblock.weight.toLocaleString()}
- var timeAgoTime = result.getblock.time;
span.text-muted.ms-2 (
include includes/time-ago-text.pug
span ago)
if (result.getblock.weight)
div.row
div.summary-table-label Weight
div.summary-table-content.font-monospace
span #{result.getblock.weight.toLocaleString()}
small wu
span.text-muted (#{new Decimal(100 * result.getblock.weight / coinConfig.maxBlockWeight).toDecimalPlaces(2)}% full)
else if (result.getblock.size)
div.row
div.summary-table-label Size
div.summary-table-content.font-monospace #{result.getblock.size.toLocaleString()}
small B
div.row
div.summary-table-label Transactions
div.summary-table-content.font-monospace #{result.getblock.nTx.toLocaleString()}
div.row
div.summary-table-label Confirmations
div.summary-table-content.font-monospace
if (result.getblock.confirmations < 6)
span.fw-bold.text-warning #{result.getblock.confirmations.toLocaleString()}
a(data-bs-toggle="tooltip", title="Fewer than 6 confirmations is generally considered 'unsettled' for high-value transactions. The applicability of this guidance may vary.")
i.fas.fa-unlock-alt
else
span.fw-bold.text-success #{result.getblock.confirmations.toLocaleString()}
a(data-bs-toggle="tooltip", title="6 confirmations is generally considered 'settled'. High-value transactions may require more; low-value transactions may require less.")
i.fas.fa-lock
else if (result.getblock.size)
+summaryItem("Size", null, "B", "Bytes")
| #{result.getblock.size.toLocaleString()}
div#progress-wrapper.mb-huge
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading transactions:
+summaryItem("Transactions")
| #{result.getblock.nTx.toLocaleString()}
+summaryItem("Confirmations")
+confirmations(result.getblock.confirmations, false)
#progress-wrapper.mb-huge
.card.shadow-sm.mb-3
.card-body
span Loading transactions:
span(id="progress-text")
div.progress.mt-2(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
.progress.mt-2(id="progress-bar", style="height: 7px;")
.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
div#main-content(style="display: none;")
div.row
div.col-xl-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Top Value Transactions
hr
#main-content(style="display: none;")
.row
.col-xl-6
+contentSection("Top Value Transactions")
.table-responsive
table.table.table-striped.table-borderless.mb-0
thead
tr
th.data-header Transaction
th.data-header.text-end Output Value
div.table-responsive
table.table.table-striped.mb-0
tbody(id="tbody-top-value-tx")
tr.font-monospace.row-prototype(style="display: none;")
td.data-tx-link
td.text-end.data-tx-value
tr.hidden-row-to-fix-striping.d-none
.col-xl-6
+contentSection("Top Size Transactions")
.table-responsive
table.table.table-striped.table-borderless.mb-0
thead
tr
th.data-header Transaction
th.data-header.text-end Size
small.fw-normal.text-muted.ms-2 (vB)
tbody(id="tbody-top-size-tx")
tr.font-monospace.row-prototype(style="display: none;")
td.data-tx-link
td.text-end.data-tx-value
tr.hidden-row-to-fix-striping.d-none
if (global.txindexAvailable)
.col-xl-6
+contentSection("Top Fee Transactions")
.table-responsive
table.table.table-striped.table-borderless.mb-0
thead
tr
th.data-header Transaction
th.data-header.text-end Output Value
th.data-header.text-end Fee
tbody(id="tbody-top-value-tx")
tbody(id="tbody-top-fee-tx")
tr.font-monospace.row-prototype(style="display: none;")
td.data-tx-link
td.text-end.data-tx-value
div.col-xl-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Top Size Transactions
hr
tr.hidden-row-to-fix-striping.d-none
div.table-responsive
table.table.table-striped.mb-0
.col-xl-6
+contentSection("Top Fee Rate Transactions")
.table-responsive
table.table.table-striped.table-borderless.mb-0
thead
tr
th.data-header Transaction
th.data-header.text-end Size
th.data-header.text-end Fee Rate
small.text-muted.fw-normal.ms-2 (sat/vB)
tbody(id="tbody-top-size-tx")
tbody(id="tbody-top-fee-rate-tx")
tr.font-monospace.row-prototype(style="display: none;")
td.data-tx-link
td.text-end.data-tx-value
if (global.txindexAvailable)
div.col-xl-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Top Fee Transactions
hr
tr.hidden-row-to-fix-striping.d-none
div.table-responsive
table.table.table-striped.mb-0
thead
tr
th.data-header Transaction
th.data-header.text-end Fee
tbody(id="tbody-top-fee-tx")
tr.font-monospace.row-prototype(style="display: none;")
td.data-tx-link
td.text-end.data-tx-value
div.col-xl-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Top Fee Rate Transactions
hr
div.table-responsive
table.table.table-striped.mb-0
thead
tr
th.data-header Transaction
th.data-header.text-end Fee Rate
tbody(id="tbody-top-fee-rate-tx")
tr.font-monospace.row-prototype(style="display: none;")
td.data-tx-link
td.text-end.data-tx-value
if (global.txindexAvailable)
div.col-lg-6.mb-3
div.card.shadow-sm(style="height: 100%;")
.col-lg-6.mb-3
+sectionTitle("Input Types")
.card.shadow-sm
div.card-body
h3.h6.mb-0 Input Types
hr
table.table.table-striped.mb-0
span#no-input-types-data.text-muted(style="display: none;") No data
table.table.table-striped.table-borderless.mb-0
tbody(id="tbody-input-types")
tr.font-monospace.row-prototype(style="display: none;")
td
small.data-tag.bg-dark.data-type
span.badge.bg-dark.data-type
td.data-count
div.col-lg-6.mb-3(class=(!global.txindexAvailable ? "col-lg-12" : ""))
div.card.shadow-sm(style="height: 100%;")
div.card-body
h3.h6.mb-0 Output Types
hr
table.table.table-striped.mb-0
tr.hidden-row-to-fix-striping.d-none
.col-lg-6.mb-3(class=(!global.txindexAvailable ? "col-lg-12" : ""))
+sectionTitle("Output Types")
.card.shadow-sm
.card-body
table.table.table-striped.table-borderless.mb-0
tbody(id="tbody-output-types")
tr.font-monospace.row-prototype(style="display: none;")
td
small.data-tag.bg-dark.data-type
span.badge.bg-dark.data-type
td.data-count
div.row
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Value
hr
tr.hidden-row-to-fix-striping.d-none
canvas(id="graph-tx-value")
.row
.col-lg-6
+contentSection("Transaction Value")
canvas(id="graph-tx-value")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Value Distribution
hr
canvas(id="chart-tx-value-distribution")
.col-lg-6
+contentSection("Transaction Value Distribution")
canvas(id="chart-tx-value-distribution")
if (global.txindexAvailable)
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Fee
hr
.col-lg-6
+contentSection("Transaction Fee")
canvas(id="graph-tx-fee")
canvas(id="graph-tx-fee")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Fee Distribution
hr
.col-lg-6
+contentSection("Transaction Fee Distribution")
canvas(id="chart-tx-fee-distribution")
canvas(id="chart-tx-fee-distribution")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Fee Rate
hr
.col-lg-6
+contentSection("Transaction Fee Rate")
canvas(id="graph-tx-fee-rate")
canvas(id="graph-tx-fee-rate")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Fee Rate Distribution
hr
.col-lg-6
+contentSection("Transaction Fee Rate Distribution")
canvas(id="chart-tx-fee-rate-distribution")
canvas(id="chart-tx-fee-rate-distribution")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Size
hr
.col-lg-6
+contentSection("Transaction Size")
canvas(id="graph-tx-size")
canvas(id="graph-tx-size")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Size Distribution
hr
.col-lg-6
+contentSection("Transaction Size Distribution")
canvas(id="chart-tx-size-distribution")
canvas(id="chart-tx-size-distribution")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Input Count
hr
.col-lg-6
+contentSection("Transaction Input Count")
canvas(id="graph-tx-inputs")
canvas(id="graph-tx-inputs")
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction Output Count
hr
canvas(id="graph-tx-outputs")
.col-lg-6
+contentSection("Transaction Output Count")
canvas(id="graph-tx-outputs")
block endOfBody
- var txidChunkSize = 10;
script(src="./js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
+graphPageScriptSetup
script(src='./js/decimal.js')
script.
var txidChunkSize = !{txidChunkSize};
var prunedBlock = !{(block && (!block.tx || block.tx.length == 0) && global.prunedBlockchain)};
@ -288,7 +237,7 @@ block endOfBody
var blockHash = "!{result.getblock.hash}";
var satsMultiplier = !{coinConfig.baseCurrencyUnit.multiplier};
var vByteSizes = false;
$(document).ready(function() {
if (!prunedBlock) {
loadTransactions(txidChunks, txidChunkSize, txidChunks.length * txidChunkSize);
@ -375,7 +324,7 @@ block endOfBody
row.removeClass("row-prototype");
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 32)}…</a>`);
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 24)}…</a>`);
row.find(".data-tx-value").text(item.value);
updateCurrencyValue(row.find(".data-tx-value"), item.value);
@ -396,7 +345,7 @@ block endOfBody
row.removeClass("row-prototype");
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 32)}…</a>`);
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 24)}…</a>`);
row.find(".data-tx-value").text(item.value);
updateCurrencyValue(row.find(".data-tx-value"), item.value);
@ -417,8 +366,8 @@ block endOfBody
row.removeClass("row-prototype");
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 32)}…</a>`);
row.find(".data-tx-value").html(`<span>${parseInt(item.value).toLocaleString()} <small>sat/${vByteSizes ? "v" : ""}B</small></span>`);
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 24)}…</a>`);
row.find(".data-tx-value").html(`<span>${parseInt(item.value).toLocaleString()}</span>`);
row.show();
@ -436,8 +385,8 @@ block endOfBody
row.removeClass("row-prototype");
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 32)}…</a>`);
row.find(".data-tx-value").html(`<span>${parseInt(item.value).toLocaleString()} <small>${vByteSizes ? "v" : ""}B</small></span>`);
row.find(".data-tx-link").html(`<a href='./tx/${item.txid}'>${item.txid.substring(0, 24)}…</a>`);
row.find(".data-tx-value").html(`<span>${parseInt(item.value).toLocaleString()}</span>`);
row.show();
@ -468,6 +417,10 @@ block endOfBody
return b.count - a.count;
});
if (sortedInputs.length == 0) {
$("#no-input-types-data").show();
}
for (var i = 0; i < sortedInputs.length; i++) {
var item = sortedInputs[i];
@ -532,6 +485,9 @@ block endOfBody
display: true,
labelString: 'Index in Block'
},
gridLines: {
color: gridLineColor
}
//ticks: {
// stepSize: 100,
//}
@ -539,7 +495,10 @@ block endOfBody
yAxes: [{
scaleLabel: {
display: true,
labelString: yLabelStr
labelString: yLabelStr,
},
gridLines: {
color: gridLineColor
}
}]
}
@ -569,9 +528,17 @@ block endOfBody
display: false
},
scales: {
xAxes: [{
gridLines: {
color: gridLineColor
}
}],
yAxes: [{
ticks: {
beginAtZero:true
},
gridLines: {
color: gridLineColor
}
}]
}

View file

@ -4,8 +4,7 @@ block headContent
title Block Block
block content
h1.h3 Block Stats
hr
+pageTitle("Block Stats")
if (rpcApiUnsupportedError)
div.alert.alert-danger(style="margin-bottom: 200px;")
@ -17,90 +16,64 @@ block content
else
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Block Range
hr
+dismissableInfoAlert("blockStatsPageNoteDismissed", "About Block Stats...")
| !{markdown("The **Block Stats** tool queries a range of blocks from Bitcoin Core and summarizes the data via graphs. These graphs sometimes expose interesting trends and sometimes show boring, steady-state operation of the network.")}
| Use the <b>Filters</b> section below to select the range of blocks to analyze and select the subset of graphs displayed.
div.clearfix
div.float-start.me-4
div(id="time-range-buttons")
h3.h6 Latest N Blocks
div.btn-group
a.btn.btn-primary.block-count-btn(href="javascript:void(0)", data-blockCount="144") 144
small.ms-2 (~1d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="432") 432
small.ms-2 (~3d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="1008") 1,008
small.ms-2 (~7d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="4320") 4,320
small.ms-2 (~30d)
div.float-start.me-4
div(id="block-selections-buttons")
h3.h6 Preconfigured
div.dropdown
button.btn.btn-outline-primary.dropdown-toggle(type="button", id="preconfigured-dropdown", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false") Selections
div.dropdown-menu(aria-labelledby="preconfigured-dropdown")
a.dropdown-item(href="javascript:void(0)", data-blocks="0-199") First 200 Blocks
a.dropdown-item(href="javascript:void(0)", data-blocks="209900-210100") First Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="419900-420100") Second Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="629900-630100") Third Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="481724-481924") SegWit Activation ±100
- var graphIds = ["fee-rates", "max-fee-rates", "total-fee", "min-fees", "max-fees", "inputs-outputs", "tx-sizes", "max-tx-sizes", "volumes", "weights-sizes", "sw-txs", "subsidy", "mediantime", "txs", "utxo_increase"];
- var graphTitles = ["Fee Rates (avg, median, min)", "Maximum Fee Rate", "Total Fees", "Minimum Tx Fee", "Maximum Tx Fee", "Input / Output Count", "Tx Sizes (avg, median, min)", "Maximum Tx Size", "Output Volume", "Block Weight and Size", "SegWit Tx Count", "Subsidy", "Timestamp", "Tx Count", "UTXO Δ"];
div.float-start
div(id="time-range-buttons")
h3.h6 Custom Range
+contentSection("Filters")
.mb-3
+blockRangeFilters
form.form-inline.me-3(id="custom-range-form")
div.input-group
input.form-control(type="text", id="custom-range-start", placeholder="min height", style="width: 125px;")
input.form-control(type="text", id="custom-range-end", placeholder="max height", style="width: 125px;")
button.btn.btn-primary(type="submit") Go
hr
div.mb-2
span.border-dotted(title="Select the list of graphs to be displayed.", data-bs-toggle="tooltip") Active Graphs
.clearfix
each graphId, graphIndex in graphIds
div.float-start.me-3.mb-1
div.form-check
input.form-check-input.graph-toggle-checkbox(type="checkbox", id=`checkbox-${graphId}` checked="checked", data-graphId=graphId, autocomplete="off")
label.form-check-label(for=("checkbox-" + graphId)) #{graphTitles[graphIndex]}
.float-start.me-3.mb-1
a.me-3(id="link-show-all", href="javascript:void(0)") show all
a(id="link-hide-all", href="javascript:void(0)") hide all
div#progress-wrapper.mb-huge
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading blocks:
span Loading blocks:
span(id="block-progress-text")
div.progress.mt-2(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
- var graphIds = ["fee-rates", "max-fee-rates", "total-fee", "min-fees", "max-fees", "inputs-outputs", "tx-sizes", "max-tx-sizes", "volumes", "weights-sizes", "sw-txs", "subsidy", "mediantime", "txs", "utxo_increase"];
- var graphTitles = ["Fee Rates (avg, median, min)", "Maximum Fee Rate", "Total Fees", "Minimum Tx Fee", "Maximum Tx Fee", "Input / Output Count", "Tx Sizes (avg, median, min)", "Maximum Tx Size", "Output Volume", "Block Weight and Size", "SegWit Tx Count", "Subsidy", "Timestamp", "Tx Count", "UTXO Δ"];
div(id="main-content", style="display: none;")
div.card.shadow-sm.mb-3
div.card-body
div.clearfix
each graphId, graphIndex in graphIds
div.float-start.me-3.mb-1
div.form-check
input.form-check-input.graph-toggle-checkbox(type="checkbox", id=`checkbox-${graphId}` checked="checked", data-graphId=graphId, autocomplete="off")
label.form-check-label(for=("checkbox-" + graphId)) #{graphTitles[graphIndex]}
div.float-start.me-3.mb-1
a.me-3(id="link-show-all", href="javascript:void(0)") show all
a(id="link-hide-all", href="javascript:void(0)") hide all
div.row.clearfix
.row.clearfix
each graphId, graphIndex in graphIds
div.col-lg-6.float-start
div.card.shadow-sm.mb-3.graph-wrapper(id=`graph-wrapper-${graphId}`)
div.card-body
h3.h6.mb-0 #{graphTitles[graphIndex]}
hr
canvas.mb-3(id=graphId)
.col-lg-6.float-start
.graph-wrapper(id=`graph-wrapper-${graphId}`)
+sectionTitle(graphTitles[graphIndex])
.card.shadow-sm.mb-3
.card-body
canvas.mb-3(id=graphId)
block endOfBody
script(src="./js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
+graphPageScriptSetup
script(src='./js/decimal.js')
script.
@ -389,6 +362,9 @@ block endOfBody
scaleLabel: {
display: false,
//labelString: names[i]
},
gridLines: {
color: gridLineColor
}
});
}
@ -423,6 +399,19 @@ block endOfBody
display: true,
labelString: 'Block'
},
gridLines: {
color: gridLineColor
}
}],
yAxes: [{
type: 'linear',
position: 'left',
scaleLabel: {
display: false,
},
gridLines: {
color: gridLineColor
}
}],
}
}

View file

@ -8,10 +8,278 @@ block headContent
block content
if (result.getblock)
h1.h3 Block
small.font-monospace(style="width: 100%;") ##{result.getblock.height.toLocaleString()}
br
small.font-monospace.word-wrap(style="width: 100%;") #{result.getblock.hash}
.mb-3
.clearfix
.float-start
if (result.getblock.previousblockhash)
a.btn.btn-sm.btn-primary.ms-2(href=`./block/${result.getblock.previousblockhash}`, title=`Block ${(result.getblock.height - 1).toLocaleString()}`, data-bs-toggle="tooltip") &laquo; Prev Block
else if (result.getblock.hash == genesisBlockHash)
span.btn.btn-sm.btn-success.ms-2 Genesis Block!
if (result.getblock.nextblockhash)
a.btn.btn-sm.btn-primary.ms-2(href=`./block/${result.getblock.nextblockhash}`, title=`Block ${(result.getblock.height + 1).toLocaleString()}`, data-bs-toggle="tooltip") Next Block
span.ms-1 &raquo;
else
span.btn.btn-sm.btn-success.ms-2 Latest Block!
.float-end
if (result.getblock.tx != null && result.getblock.tx.length > 0)
a.btn.btn-primary.btn-sm.me-2(href=`./block-analysis/${result.getblock.hash}`, title="See a summary analysis of all transactions in this block.", data-bs-toggle="tooltip")
i.fas.fa-angle-double-down.me-2
| Block Analysis
hr
include includes/block-content.pug
+pageTitle(`Block #${result.getblock.height.toLocaleString()}`, result.getblock.hash, true)
+pageTabs(["Details", "JSON"])
- var txCount = result.getblock.nTx;
.tab-content
+pageTab("Details", true)
if (global.specialBlocks && global.specialBlocks[result.getblock.hash])
- var sbInfo = global.specialBlocks[result.getblock.hash];
+funAlert(sbInfo.alertBodyHtml, sbInfo.summary, sbInfo.referenceUrl)
.clearfix
.float-end
if (`${userSettings["blockPageShowFeeSummary"]}` == "false")
a.btn.btn-primary.btn-sm.me-2.mb-2(href=`./changeSetting?name=blockPageShowFeeSummary&value=true`, title="Display fee details for this block.", data-bs-toggle="tooltip")
i.far.fa-plus-square.me-2
| Fee Details
if (`${userSettings["blockPageShowTechSummary"]}` == "false")
a.btn.btn-primary.btn-sm.me-2.mb-2(href=`./changeSetting?name=blockPageShowTechSummary&value=true`, title="Display technical details for this block.", data-bs-toggle="tooltip")
i.far.fa-plus-square.me-2
| Technical Details
+contentSection("Summary")
+summaryRow(2 + (result.getblock.miner ? 1 : 0) + (result.blockstats ? 1 : 0))
+summaryItem("Date")
+timestamp(result.getblock.time, true)
+summaryItem("Confirmations")
+confirmations(result.getblock.confirmations, false)
if (result.getblock.miner)
+summaryItem("Miner")
if (result.getblock.miner.identifiedBy)
span.badge.bg-primary(data-bs-toggle="tooltip", title=`Identified by: ${result.getblock.miner.identifiedBy}`) #{result.getblock.miner.name}
else
small.badge.bg-primary #{result.getblock.miner.name}
if (result.blockstats)
+summaryItem("Total Output", "Total value of all transaction outputs")
if (result.blockstats.total_out)
- var currencyValue = new Decimal(result.blockstats.total_out).plus(new Decimal(result.blockstats.totalfee)).plus(new Decimal(result.blockstats.subsidy));
- currencyValue = currencyValue.dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./includes/value-display.pug
else
span 0
if (result.blockstats && result.blockstats.ins && result.blockstats.ins > 0)
+contentSection("Fee Details", true, "blockPageShowFeeSummary", true)
+summaryRow(3 + (result.blockstats.feerate_percentiles ? 1 : 0))
+summaryItem("Total Fees")
if (result.blockstats.totalfee)
- var currencyValue = new Decimal(result.blockstats.totalfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./includes/value-display.pug
else
- var currencyValue = new Decimal(result.getblock.totalFees);
include ./includes/value-display.pug
if (result.blockstats.feerate_percentiles)
+summaryItem("Rate Percentiles", null, "sat/vB")
- var percentiles = [[10, 0], [50, 2], [90, 4]];
if (true)
.d-flex.justify-content-start.justify-content-md-center
each item, itemIndex in percentiles
.text-start.text-md-center(style="width: 75px;")
span.text-muted #{item[0]}
sup th
br
| #{result.blockstats.feerate_percentiles[item[1]]}
+summaryItem("Min / Max Rates", null, "sat/vB")
if (result.blockstats.minfeerate || result.blockstats.minfeerate == 0)
span #{result.blockstats.minfeerate.toLocaleString()}
else
span ?
span.mx-2 -
if (result.blockstats.maxfeerate)
span #{result.blockstats.maxfeerate.toLocaleString()}
else
span ?
+summaryItem("Min / Max Values")
if (result.blockstats.minfee)
- var currencyValue = new Decimal(result.blockstats.minfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./includes/value-display.pug
else
span 0
br
if (result.blockstats.maxfee)
- var currencyValue = new Decimal(result.blockstats.maxfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./includes/value-display.pug
else
span 0
+contentSection("Technical Details", true, "blockPageShowTechSummary")
+summaryRow(6 + (result.getblock.weight ? 1 : 0) + (result.getblock.size ? 1 : 0) + (result.blockstats ? 1 : 0) + (result.blockstats && result.blockstats.utxo_increase ? 1 : 0) + (result.blockstats && result.blockstats.maxtxsize ? 1 : 0))
if (result.getblock.weight)
+summaryItem("Weight", null, "wu", "Weight Units")
span #{result.getblock.weight.toLocaleString()}
- var full = new Decimal(result.getblock.weight).dividedBy(coinConfig.maxBlockWeight).times(100);
- var full2 = full.toDP(0);
small.text-muted.ms-1
| (
span.border-dotted(title=`${full.toDP(2)}% full`, data-bs-toggle="tooltip")
if (full >= 99 || full2 == 99)
span.text-success 99+%
else
span.text-primary #{full2}%
| )
if (result.getblock.size)
+summaryItem("Size", null, "B", "Bytes")
| #{result.getblock.size.toLocaleString()}
if (result.blockstats)
+summaryItem("Inputs / Outputs", "Total inputs and outputs across all transactions in the block")
if (result.blockstats.ins)
| #{result.blockstats.ins.toLocaleString()}
else
| 0
span.text-muted.mx-1 /
if (result.blockstats.outs)
| #{result.blockstats.outs.toLocaleString()}
else
| 1
+summaryItem("Difficulty")
- var difficultyData = utils.formatLargeNumber(result.getblock.difficulty, 3);
span(title=parseFloat(result.getblock.difficulty).toLocaleString(), data-bs-toggle="tooltip")
span #{difficultyData[0]}
span x 10
sup #{difficultyData[1].exponent}
if (result.blockstats && result.blockstats.utxo_increase)
+summaryItem("UTXO Δ")
- var sizePlusMinus = (result.blockstats.utxo_size_inc > 0) ? "+" : "-";
- var sizeDeltaData = utils.formatLargeNumber(Math.abs(result.blockstats.utxo_size_inc), 1);
- var plusMinus = (result.blockstats.utxo_increase > 0) ? "+" : "";
span #{plusMinus}#{result.blockstats.utxo_increase.toLocaleString()}
if (result.blockstats && result.blockstats.maxtxsize)
+summaryItem("Min / Max Tx Size", null, "B", "Bytes")
if (result.blockstats.mintxsize)
span #{result.blockstats.mintxsize.toLocaleString()}
else
span 0
span.text-muted.mx-1 -
if (result.blockstats.maxtxsize)
span #{result.blockstats.maxtxsize.toLocaleString()}
else
span 0
+summaryItem("Version")
| 0x#{result.getblock.versionHex}
+summaryItem("Nonce")
| #{result.getblock.nonce}
+summaryItem("Bits")
| #{result.getblock.bits}
+summaryItem("Merkle Root")
| #{utils.ellipsizeMiddle(result.getblock.merkleroot, 12)}
+copyTextButton(result.getblock.merkleroot)
+summaryItem("Chain Work", null, "hashes")
- var chainworkData = utils.formatLargeNumber(parseInt("0x" + result.getblock.chainwork), 2);
span #{chainworkData[0]}
span x 10
sup #{chainworkData[1].exponent}
if (!result.getblock.tx || result.getblock.tx.length == 0)
+contentSection(`${txCount.toLocaleString()} Transaction${txCount>1?'s':''}`)
span.me-2(title="This data is unavailable due to blockchain pruning", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger
| Transaction details unavailable due to blockchain pruning
else
+contentSection(`${txCount.toLocaleString()} Transaction${txCount == 1 ? "" : "s"}`, false, "blockPageShowTxDetails", true, false)
+txList(result.transactions, txCount, limit, offset, result.txInputsByTransaction, {sharedBlockHeight: result.getblock.height})
+pageTab("JSON")
- var blockDetails = JSON.parse(JSON.stringify(result.getblock));
- blockDetails.tx = "See 'Transaction IDs'";
- var pillTabs = ["Block Summary", "Transaction IDs"];
if (result.blockstats)
- pillTabs.push("Block Stats");
+pillTabs(pillTabs)
div.tab-content
+pageTab("Block Summary", true)
+contentSection("Block Summary")
div.highlight
pre
code.json #{JSON.stringify(blockDetails, null, 4)}
+pageTab("Transaction IDs")
+contentSection("Transaction IDs")
div.highlight
pre
code.json #{JSON.stringify(result.getblock.tx, null, 4)}
if (result.blockstats)
+pageTab("Block Stats")
+contentSection("Block Stats")
div.highlight
pre
code.json #{JSON.stringify(result.blockstats, null, 4)}

View file

@ -4,8 +4,7 @@ block headContent
title Blocks
block content
h1.h3 Blocks
hr
+pageTitle("Blocks")
if (blocks)
nav(aria-label="Page navigation")
@ -19,21 +18,15 @@ block content
a.page-link(href=(sort == "asc" ? "javascript:void(0)" : `./blocks?limit=${limit}&offset=0&sort=asc`))
span(aria-hidden="true") Oldest blocks first
div.card.shadow-sm.mb-3
div.card-body
include includes/blocks-list.pug
+contentSection
include includes/blocks-list.pug
if (blockCount > limit)
.border.border-2.mt-4
.mt-4
+pagination(limit, offset, sort, blockCount, paginationBaseUrl, "center", true)
if (blockCount > limit)
- var pageNumber = offset / limit + 1;
- var pageCount = Math.floor(blockCount / limit);
- if (pageCount * limit < blockCount) {
- pageCount++;
- }
- var paginationUrlFunction = function(x) {
- return paginationBaseUrl + "?limit=" + limit + "&offset=" + ((x - 1) * limit + "&sort=" + sort);
- }
div.pt-5
include includes/pagination.pug
else
p No blocks found

View file

@ -4,12 +4,9 @@ block headContent
title Changelog
style.
code { padding: 0px 3px; border: solid 1px #ccc; border-radius: 3px; background-color: #ccc; color: black; }
block content
h1.h3 Changelog
hr
+pageTitle("Changelog / Release Notes")
div.card.shadow-sm.mb-3
div.card-body
| !{changelogHtml}
+contentSection
| !{changelogHtml}

View file

@ -1,8 +1,7 @@
extends layout
block content
h1.h3 RPC Connect
hr
+pageTitle("RPC Connect")
form(method="post", action="./connect")
input(type="hidden", name="_csrf", value=csrfToken)

View file

@ -4,25 +4,25 @@ block headContent
title Difficulty History
block content
h1.h3 Difficulty History
hr
+pageTitle("Difficulty History")
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Explanation
hr
+dismissableInfoAlert("diffHistPageNoteDismissed", "About the Difficulty Adjustment...")
h6.mb-2 About the Difficulty Adjustment
ul.mb-0
li Mining difficulty adjusts automatically every #{coinConfig.difficultyAdjustmentBlockCount.toLocaleString()} blocks.
li The adjustment aims to maintain an average block-mining time of #{coinConfig.targetBlockTimeMinutes} minutes.
li A growth in the difficulty indicates that the average block-mining time during the previous adjustment epoch was less than #{coinConfig.targetBlockTimeMinutes} minutes (due to more miners joining the network and searching for blocks).
li A drop in difficulty indicates miners have left the network so finding each block is adjusted to be easier for the smaller number remaining.
li The numeric Difficulty is a multiple of the difficulty of finding the easiest block (Block #0) - e.g. blocks in BTC mainnet epoch 308 are over 16 trillion times harder to mine than those in epoch 0.
ul.mb-0.ms-n3
li Mining difficulty adjusts automatically every #{coinConfig.difficultyAdjustmentBlockCount.toLocaleString()} blocks.
li The adjustment aims to maintain an average block-mining time of #{coinConfig.targetBlockTimeMinutes} minutes.
li An increase in the difficulty indicates that the average block-mining time during the previous adjustment epoch was less than #{coinConfig.targetBlockTimeMinutes} minutes (due to more miners joining the network and searching for blocks).
li A decrease in difficulty indicates miners have left the network so finding each block is adjusted to be easier for the smaller number remaining.
li The numeric Difficulty is a multiple of the difficulty of finding the easiest block (Block #0) - e.g. blocks in BTC mainnet epoch 308 are over 16 trillion times harder to mine than those in epoch 0.
div#progress-wrapper.mb-huge
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading data:
span Loading data:
span(id="progress-text")
div.progress.mt-2(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
@ -31,27 +31,34 @@ block content
div#main-content(style="display: none;")
div.row
div.col
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Difficulty History
hr
+contentSection("Difficulty History")
canvas(id="graph-diff-hist")
canvas(id="graph-diff-hist")
div.row
div.row
div.col
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Difficulty Change (clamped to ±100%)
hr
.clearfix
.float-start
+sectionTitle("Difficulty Δ")
.float-start
small.text-muted.ms-2
| (clamped to ±100%)
canvas(id="graph-diff-change")
+contentSection
canvas(id="graph-diff-change")
block endOfBody
script(src="./js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
+graphPageScriptSetup
script(src='./js/decimal.js')
script.
Chart.defaults.global.defaultFontSize = 14;
script.
var blockCount = !{blockCount};
@ -143,6 +150,9 @@ block endOfBody
display: true,
labelString: 'Difficulty Epoch'
},
gridLines: {
color: gridLineColor
}
//ticks: {
// stepSize: 100,
//}
@ -151,6 +161,20 @@ block endOfBody
scaleLabel: {
display: true,
labelString: yLabelStr
},
gridLines: {
color: gridLineColor
},
ticks: {
callback: function(value, index, values) {
if (value > 1000) {
var exp = Math.floor(Math.log10(value));
return (value / Math.pow(10, exp)).toLocaleString() + "e" + exp;
} else {
return value;
}
}
}
}]
}

View file

@ -1,16 +1,15 @@
extends layout
block content
h1 Error
hr
+pageTitle("Error")
if (message)
pre
code.json.bg-light !{message}
code.json.bg-body !{message}
else
p Unknown error
if (error)
h2 #{error.status}
pre
code.json.bg-light #{error.stack}
code.json.bg-body #{error.stack}

View file

@ -4,52 +4,57 @@ block headContent
title #{coinConfig.name} Fun
block content
h1.h3 #{coinConfig.name} Fun
hr
+pageTitle(`${coinConfig.name} Fun`)
div.card.shadow-sm.mb-3
div.card-body
p Below is a list of fun/interesting things in the #{coinConfig.name} blockchain. Some are historical firsts, others are just fun or cool.
div.table-responsive
table.table.table-striped
thead
tr
th.data-header Date
th.data-header Description
th.data-header Link
th.data-header Reference
tbody
each item, index in coinConfig.historicalData
if (item.chain == activeBlockchain)
tr
td.data-cell #{item.date}
td.data-cell #{item.summary}
td.data-cell.font-monospace
if (item.type == "tx")
a(href=`./tx/${item.txid}@${item.blockHeight}`, title=item.txid, data-bs-toggle="tooltip") Tx #{utils.ellipsize(item.txid, 23)}
else if (item.type == "block")
a(href=`./block/${item.blockHash}`, title=`Block ${item.blockHash}`, data-bs-toggle="tooltip") Block #{utils.ellipsize(item.blockHash, 20)}
else if (item.type == "blockheight")
a(href=`./block/${item.blockHash}`) Block ##{item.blockHeight}
else if (item.type == "address")
a(href=`./address/${item.address}`, title=item.address, data-bs-toggle="tooltip") Address #{utils.ellipsize(item.address, 18)}
else if (item.type == "link")
a(href=item.url) #{item.url.substring(0, 20)}...
+dismissableInfoAlert("funPageNoteDismissed", "About Bitcoin Fun...")
.mb-2 This is a curated list of fun and interesting things in the blockchain or related to the underlying data:
ul
li Historical firsts
li Technical quirks and oddities
li Cultural references
.mb-0 Help curating this list is welcome! You can submit new items by opening an issue or PR on <a href="https://github.com/janoside/btc-rpc-explorer">Github</a>.
+contentSection
.table-responsive
table.table.table-borderless.table-striped
thead
tr
th.data-header Date
th.data-header Item
th.data-header Reference
tbody
each item, index in coinConfig.historicalData
if (item.chain == activeBlockchain)
tr
td.data-cell #{item.date}
if (true)
td.data-cell
if (item.referenceUrl && item.referenceUrl.trim().length > 0)
- var matches = item.referenceUrl.match(/^https?\:\/\/([^\/:?#]+)(?:[\/:?#]|$)/i);
if (item.type == "tx")
a(href=`./tx/${item.txid}@${item.blockHeight}`) #{item.summary}
else if (item.type == "block")
a(href=`./block/${item.blockHash}`) #{item.summary}
else if (item.type == "blockheight")
a(href=`./block/${item.blockHash}`) #{item.summary}
else if (item.type == "address")
a(href=`./address/${item.address}`) #{item.summary}
else if (item.type == "link")
a(href=item.url) #{item.summary}
td.data-cell
if (item.referenceUrl && item.referenceUrl.trim().length > 0)
- var matches = item.referenceUrl.match(/^https?\:\/\/([^\/:?#]+)(?:[\/:?#]|$)/i);
- var domain = null;
- var domain = matches && matches[1];
- var domain = null;
- var domain = matches && matches[1];
if (domain)
a(href=item.referenceUrl, rel="nofollow") #{domain}
i.fas.fa-external-link-alt
else
a(href=item.referenceUrl, rel="nofollow") Reference
if (domain)
a(href=item.referenceUrl, rel="nofollow") #{domain}
i.fas.fa-external-link-alt
else
span -
a(href=item.referenceUrl, rel="nofollow") Reference
else
span -

View file

@ -1,483 +0,0 @@
div.mb-2
if (result.getblock.previousblockhash)
a.btn.btn-sm.btn-primary.mb-1(href=`./block/${result.getblock.previousblockhash}`) &laquo; Prev Block:
span.font-monospace ##{(result.getblock.height - 1).toLocaleString()}
else if (result.getblock.hash == genesisBlockHash)
span.btn.btn-sm.btn-secondary.disabled.mb-1 &laquo; Prev Block: none (genesis block)
span.mx-2
if (result.getblock.nextblockhash)
a.btn.btn-sm.btn-primary.mb-1(href=`./block/${result.getblock.nextblockhash}`) Next Block:
span.font-monospace ##{(result.getblock.height + 1).toLocaleString()}
span &raquo;
else
a.btn.btn-sm.btn-secondary.disabled.mb-1 Next Block: N/A
small (latest block)
ul.nav.nav-tabs.mb-3
li.nav-item
a.nav-link.active(data-bs-toggle="tab", href="#tab-details", role="tab") Details
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json", role="tab") JSON
- var txCount = result.getblock.nTx;
div.tab-content
div.tab-pane.active(id="tab-details", role="tabpanel")
if (global.specialBlocks && global.specialBlocks[result.getblock.hash])
div.alert.alert-primary.shadow-sm.pb-0
div.float-start(style="width: 55px; height: 55px; font-size: 18px;")
i.fas.fa-certificate.fa-2x(style="margin-top: 10px;")
h4.alert-heading.h6.fw-bold #{coinConfig.name} Fun
// special transaction info
- var sbInfo = global.specialBlocks[result.getblock.hash];
if (sbInfo.alertBodyHtml)
p
span !{sbInfo.alertBodyHtml}
if (sbInfo.referenceUrl && sbInfo.referenceUrl.trim().length > 0 && sbInfo.alertBodyHtml.indexOf(sbInfo.referenceUrl) == -1)
span &nbsp;
a(href=sbInfo.referenceUrl) Read more
else
p
span #{sbInfo.summary}
if (sbInfo.referenceUrl && sbInfo.referenceUrl.trim().length > 0)
span &nbsp;
a(href=sbInfo.referenceUrl) Read more
div.row
- var sumTableLabelClass = (result.blockstats != null ? "summary-split-table-label" : "summary-table-label");
- var sumTableValueClass = (result.blockstats != null ? "summary-split-table-content" : "summary-table-content");
div.mb-3(class=(result.blockstats != null ? "col-md-6 pr-0" : "col-md-12"))
div.card.shadow-sm(style="height: 100%;")
div.card-body.px-2.px-md-3
h3.h6.mb-0 Summary
hr
div.clearfix
div.row
div(class=sumTableLabelClass) Date
div.font-monospace(class=sumTableValueClass)
- var timestampHuman = result.getblock.time;
include ./timestamp-human.pug
small.ms-1 utc
- var timeAgoTime = result.getblock.time;
span.text-muted.ms-2 (
include ./time-ago-text.pug
span ago)
if (result.blockstats)
div.row
div(class=sumTableLabelClass)
span.border-dotted(title="Total value of all transaction outputs", data-bs-toggle="tooltip")
span Total Output
div.font-monospace(class=sumTableValueClass)
if (result.blockstats.total_out)
- var currencyValue = new Decimal(result.blockstats.total_out).plus(new Decimal(result.blockstats.totalfee)).plus(new Decimal(result.blockstats.subsidy));
- currencyValue = currencyValue.dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
span 0
if (result.blockstats)
div.row
div(class=sumTableLabelClass)
span.border-dotted(title="Total number of inputs and outputs", data-toggle="tooltip")
span In #
span.text-muted.font-weight-normal.mx-1 /
span Out #
div.text-monospace(class=sumTableValueClass)
if (result.blockstats.ins)
span #{result.blockstats.ins.toLocaleString()}
else
span 0
span.text-muted.mx-1 /
if (result.blockstats.outs)
span #{result.blockstats.outs.toLocaleString()}
else
span 1
if (result.blockstats)
div.row
div(class=sumTableLabelClass)
span.border-dotted(title="Change in number (and size) of UTXO set.", data-toggle="tooltip")
span UTXO Δ
div.text-monospace(class=sumTableValueClass)
- var sizePlusMinus = (result.blockstats.utxo_size_inc > 0) ? "+" : "-";
- var sizeDeltaData = utils.formatLargeNumber(Math.abs(result.blockstats.utxo_size_inc), 1);
- var plusMinus = (result.blockstats.utxo_increase > 0) ? "+" : "";
if (result.blockstats.utxo_increase)
span #{plusMinus}#{result.blockstats.utxo_increase.toLocaleString()}
span.text-muted (#{sizePlusMinus}#{sizeDeltaData[0]}
small #{sizeDeltaData[1].abbreviation}B
span )
else
span 0
if (result.blockstats && result.blockstats.maxtxsize)
div.row
div(class=sumTableLabelClass) Min, Max Tx Size
div.text-monospace(class=sumTableValueClass)
if (result.blockstats.mintxsize)
span #{result.blockstats.mintxsize.toLocaleString()}
else
span 0
span.text-muted.mx-1 -
if (result.blockstats.maxtxsize)
span #{result.blockstats.maxtxsize.toLocaleString()}
small B
else
span 0
if (result.getblock.coinbaseTx)
- var blockRewardMax = coinConfig.blockRewardFunction(result.getblock.height, activeBlockchain);
- var coinbaseTxTotalOutputValue = new Decimal(0);
each vout in result.getblock.coinbaseTx.vout
- coinbaseTxTotalOutputValue = coinbaseTxTotalOutputValue.plus(new Decimal(vout.value));
if (parseFloat(coinbaseTxTotalOutputValue) < blockRewardMax)
div.row
div(class=sumTableLabelClass)
span.border-dotted(data-bs-toggle="tooltip" title="The miner of this block failed to collect this value. As a result, it is permanently lost.") Value Destroyed
div.font-monospace.text-danger(class=sumTableValueClass)
- var currencyValue = new Decimal(blockRewardMax).minus(coinbaseTxTotalOutputValue);
include ./value-display.pug
else if (result.getblock.subsidy != null)
div.row
div(class=sumTableLabelClass)
span.border-dotted(title="Value of the block subsidy", data-bs-toggle="tooltip")
span Subsidy
div.font-monospace(class=sumTableValueClass)
- var currencyValue = new Decimal(result.getblock.subsidy);
include ./value-display.pug
if (result.getblock.weight)
div.row
div(class=sumTableLabelClass) Weight
div.text-monospace(class=sumTableValueClass)
span #{result.getblock.weight.toLocaleString()}
small wu
span.text-muted (#{new Decimal(100 * result.getblock.weight / coinConfig.maxBlockWeight).toDecimalPlaces(2)}% full)
if (result.getblock.size)
div.row
div(class=sumTableLabelClass) Size
div.text-monospace(class=sumTableValueClass) #{result.getblock.size.toLocaleString()}
small B
if (result.getblock.miner)
div.row
div(class=sumTableLabelClass) Miner
div.font-monospace(class=sumTableValueClass)
if (result.getblock.miner.identifiedBy)
span.badge.bg-primary(data-bs-toggle="tooltip", title=`Identified by: ${result.getblock.miner.identifiedBy}`) #{result.getblock.miner.name}
else
small.data-tag.bg-primary #{result.getblock.miner.name}
div.row
div(class=sumTableLabelClass) Confirmations
div.font-monospace(class=sumTableValueClass)
if (result.getblock.confirmations < 6)
span.fw-bold.text-warning #{result.getblock.confirmations.toLocaleString()}
a(data-bs-toggle="tooltip", title="Fewer than 6 confirmations is generally considered 'unsettled' for high-value transactions. The applicability of this guidance may vary.")
i.fas.fa-unlock-alt
else
span.fw-bold.text-success #{result.getblock.confirmations.toLocaleString()}
a(data-bs-toggle="tooltip", title="6 confirmations is generally considered 'settled'. High-value transactions may require more; low-value transactions may require less.")
i.fas.fa-lock
if (result.blockstats)
div.col-md-6.mb-3
div.card.shadow-sm(style="height: 100%;")
div.card-body.px-2.px-md-3
h3.h6.mb-0 Fees Summary
hr
div.clearfix
if (result.blockstats.ins == 0 || !result.blockstats.ins)
span.font-monospace N/A (no inputs)
else
div.row
div.summary-split-table-label Total
div.summary-split-table-content.font-monospace
if (result.blockstats.totalfee)
- var currencyValue = new Decimal(result.blockstats.totalfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
- var currencyValue = new Decimal(result.getblock.totalFees);
include ./value-display.pug
if (result.blockstats)
div.row
div.summary-split-table-label Rate Percentiles
br
small.fw-normal (sat/vB)
div.summary-split-table-content.font-monospace
div.clearfix
each item, itemIndex in [10, 25, 50, 75, 90]
div.float-start.me-3
span.fw-bold #{item}%
br
if (result.blockstats.feerate_percentiles)
span #{JSON.stringify(result.blockstats.feerate_percentiles[itemIndex])}
else
span -
if (result.getblock.totalFees > 0)
div.row
div.summary-split-table-label
span.border-dotted(title="These are the fee rates (fee per tx weight) for transactions in this block - min, avg, and max.", data-bs-toggle="tooltip") Fee Rates
br
small.fw-normal (min, avg, max)
div.summary-split-table-content.font-monospace
if (result.blockstats.minfeerate)
span #{result.blockstats.minfeerate.toLocaleString()}
small.ms-1 sat/vB
else
span ?
br
if (result.blockstats.avgfeerate)
span #{result.blockstats.avgfeerate.toLocaleString()}
small.ms-1 sat/vB
else
- var currencyValue = new Decimal(result.getblock.totalFees).dividedBy(result.getblock.strippedsize).times(coinConfig.baseCurrencyUnit.multiplier).toDecimalPlaces(1);
span #{currencyValue}
br
if (result.blockstats.maxfeerate)
span #{result.blockstats.maxfeerate.toLocaleString()}
small.ms-1 sat/vB
else
span ?
if (result.blockstats)
div.row
div.summary-split-table-label
span.border-dotted(title="These are the overall fee values (min, avg, max) for transactions included in this block. The min and max values correspond to specific transactions in this block; the avg is an estimate.", data-bs-toggle="tooltip") Fee Values
br
small.fw-normal (min, avg, max)
div.summary-split-table-content.font-monospace
if (result.blockstats.minfee)
- var currencyValue = new Decimal(result.blockstats.minfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
span 0
br
if (result.blockstats.avgfee)
- var currencyValue = new Decimal(result.blockstats.avgfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
span 0
br
if (result.blockstats.maxfee)
- var currencyValue = new Decimal(result.blockstats.maxfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
span 0
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
div.clearfix
div.float-left.mr-2
h3.h6.mb-0 Technical Details
div.float-right
if (!session.blockPageShowTechSummary || session.blockPageShowTechSummary == "true")
a(href="./changeSetting?name=blockPageShowTechSummary&value=false") hide
else
a(href="./changeSetting?name=blockPageShowTechSummary&value=true") show
div(id="tech-details-wrapper", class=(session.blockPageShowTechSummary == "true" ? "" : "d-none"))
hr
div.row
div.col-md-6
div.row
div.summary-split-table-label Difficulty
div.summary-split-table-content.font-monospace
- var difficultyData = utils.formatLargeNumber(result.getblock.difficulty, 3);
span(title=parseFloat(result.getblock.difficulty).toLocaleString(), data-bs-toggle="tooltip")
span #{difficultyData[0]}
span x 10
sup #{difficultyData[1].exponent}
div.row
div.summary-split-table-label Version
div.summary-split-table-content.font-monospace 0x#{result.getblock.versionHex}
span.text-muted (decimal: #{result.getblock.version})
div.row
div.summary-split-table-label Nonce
div.summary-split-table-content.font-monospace #{result.getblock.nonce}
div.row
div.summary-split-table-label Bits
div.summary-split-table-content.font-monospace #{result.getblock.bits}
div.row
div.summary-split-table-label Merkle Root
div.summary-split-table-content.font-monospace #{result.getblock.merkleroot}
div.row
div.summary-split-table-label Chainwork
div.summary-split-table-content.font-monospace
- var chainworkData = utils.formatLargeNumber(parseInt("0x" + result.getblock.chainwork), 2);
span #{chainworkData[0]}
span x 10
sup #{chainworkData[1].exponent}
small hashes
span.text-muted (#{result.getblock.chainwork.replace(/^0+/, '')})
if (!result.getblock.tx || result.getblock.tx.length == 0)
div.card.shadow-sm.mb-3: div.card-body.px-2.px-md-3
h2.h6.mb-0.d-inline-block #{txCount.toLocaleString()} Transaction#{txCount>1?'s':''}
.text-muted.pt-2
span.me-2(title="This data is unavailable due to blockchain pruning", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger
| Details unavailable due to blockchain pruning
else
div.card.shadow-sm.mb-3: div.card-body.px-2.px-md-3
div.row
div.col-md-4
h2.h6.mb-0.d-inline-block #{txCount.toLocaleString()}
if (txCount == 1)
span Transaction
else
span Transactions
if (result.getblock.tx != null && result.getblock.tx.length > 0)
a.ms-2.border-dotted(href=`./block-analysis/${result.getblock.hash}`, title="See a summary analysis of all transactions in this block.", data-bs-toggle="tooltip") See Block Analysis &raquo;
if (false || (!config.demoSite && !crawlerBot && txCount > 20))
div.col-md-8.text-end
small.me-1.text-muted Show
div.btn-group(role="group")
a.btn.btn-sm.btn-primary.px-2.py-0(href=`${paginationBaseUrl}?limit=20`, class=((limit == 20 && txCount > limit) ? "active" : false)) 20
if (txCount > 50)
a.btn.btn-sm.btn-primary.px-2.py-0(href=`${paginationBaseUrl}?limit=50` class=(limit == 50 ? "active" : false)) 50
if (txCount > 100)
a.btn.btn-sm.btn-primary.px-2.py-0(href=`${paginationBaseUrl}?limit=100`, class=(limit == 100 ? "active" : false)) 100
a.btn.btn-sm.btn-primary.px-2.py-0(href=`${paginationBaseUrl}?limit=3000`, class=(limit >= txCount ? "active" : false)) all
hr
- var fontawesomeInputName = "sign-in-alt";
- var fontawesomeOutputName = "sign-out-alt";
div
- tx_height_param = !global.txindexAvailable ? `@${result.getblock.height}` : ''
each tx, txIndex in result.transactions
div.card.shadow-sm(class=(" " + ((txIndex < (result.transactions.length - 1) || txCount > limit) ? "mb-3" : "")))
div.card-header.font-monospace
if (tx && tx.txid)
span(title=`Index in Block: #${(txIndex + offset).toLocaleString()}`, data-bs-toggle="tooltip") ##{(txIndex + offset).toLocaleString()}
span &ndash;&nbsp;
a(href=`./tx/${tx.txid}${tx_height_param}`) #{tx.txid}
if (global.specialTransactions && global.specialTransactions[tx.txid])
span &nbsp;
a(data-bs-toggle="tooltip", title=`${coinConfig.name} Fun! See transaction for details`)
i.fas.fa-certificate.text-primary
div.card-body.px-2.px-md-3
if (true)
- var txInputs = result.txInputsByTransaction[tx.txid];
- var blockHeight = result.getblock.height;
include ./transaction-io-details.pug
if (!crawlerBot && 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.tab-pane(id="tab-json", role="tabpanel")
- var blockDetails = JSON.parse(JSON.stringify(result.getblock));
- blockDetails.tx = "See 'Transaction IDs'";
ul.nav.nav-pills.mb-3
li.nav-item
a.nav-link.active(data-bs-toggle="tab", href="#tab-json-block-summary", role="tab") Block Summary
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json-tx-ids", role="tab") Transaction IDs
if (result.blockstats)
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json-blockstats", role="tab") Block Stats
div.tab-content
div.tab-pane.active(id="tab-json-block-summary", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 Block Summary
hr
div.highlight
pre
code.json.bg-light #{JSON.stringify(blockDetails, null, 4)}
div.tab-pane(id="tab-json-tx-ids", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 Transaction IDs
hr
div.highlight
pre
code.json.bg-light #{JSON.stringify(result.getblock.tx, null, 4)}
if (result.blockstats)
div.tab-pane(id="tab-json-blockstats", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 Block Stats
hr
div.highlight
pre
code.json.bg-light #{JSON.stringify(result.blockstats, null, 4)}

View file

@ -1,9 +1,10 @@
div.table-responsive
.table-responsive
table.table.table-borderless.table-striped.mb-0
thead
tr
//th
th
if (!hideBlockIndex)
th
th.data-header.text-end Height
@ -13,7 +14,7 @@ div.table-responsive
th.data-header.text-end Age
th.data-header.text-end
span.border-dotted(title="Time To Mine - The time it took to mine this block after the previous block. 'Fast' blocks (mined in < 5min) are shown in green; 'slow' blocks (mined in > 15min) are shown in red.", data-bs-toggle="tooltip") T.T.M.
span.border-dotted(title="<b>Time To Mine</b><br/>The time it took to mine this block after the previous block. <span class='text-success'>Fast</span> blocks (< 5min) and <span class='text-danger'>Slow</span> blocks (> 15min) are highlighted.", data-bs-toggle="tooltip", data-bs-html="true") TTM
th.data-header.text-end Miner
th.data-header.text-end
span.border-dotted(title="The number of transactions included in each block.", data-bs-toggle="tooltip") N(tx)
@ -50,16 +51,18 @@ div.table-responsive
if (block && ((sort == "desc" && (blockIndex < blocks.length - 1 || block.height == 0)) || (sort == "asc" && (block.height == 0 || blockIndex > 0))))
tr
td
if (sort == "desc")
small.text-muted #{(blockIndex + offset + 1).toLocaleString()}
else
small.text-muted #{(blockIndex + offset).toLocaleString()}
if (!hideBlockIndex)
td
if (sort == "desc")
small.text-muted #{(blockIndex + offset + 1).toLocaleString()}
else
small.text-muted #{(blockIndex + offset).toLocaleString()}
td.data-cell.font-monospace.text-end
if (global.specialBlocks && global.specialBlocks[block.hash])
a(data-bs-toggle="tooltip", title=`${coinConfig.name} Fun! See block for details`)
i.fas.fa-certificate.text-primary.me-1
a(data-bs-toggle="tooltip", title=`<b>${coinConfig.name} Fun!</b><br/>There's something special about this block...<br/>Click to read more.`, data-bs-html="true")
span.me-2
+funItemIcon
a(href=`./block-height/${block.height}`) #{block.height.toLocaleString()}
@ -78,8 +81,7 @@ div.table-responsive
if (!hideTimestampColumn)
td.data-cell.font-monospace.text-end
- var timestampHuman = block.time;
include timestamp-human.pug
+timestamp(block.time, false)
td.data-cell.font-monospace.text-end
@ -122,9 +124,9 @@ div.table-responsive
span.badge.bg-primary(data-bs-toggle="tooltip", title=`Identified by: ${block.miner.identifiedBy}`) #{utils.ellipsize(block.miner.name, 14)}
else if (block.height < global.pruneHeight)
span.border-dotted(title="Determining the miner of a block requires that the block not be pruned. Sadly, this block has been pruned.", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger.me-2
| ?
span(title="This data is unavailable due to blockchain pruning", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger
else
span ?
@ -145,9 +147,13 @@ div.table-responsive
span 0
td.data-cell.font-monospace.text-end
if block.totalFees && block.strippedsize
if (block.totalFees && block.strippedsize)
- var currencyValue = new Decimal(block.totalFees).dividedBy(block.strippedsize).times(coinConfig.baseCurrencyUnit.multiplier).toDecimalPlaces(0);
span #{currencyValue}
if (currencyValue > 0)
span #{currencyValue}
else
span -
else
span(title="This data is unavailable due to blockchain pruning", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger

View file

@ -4,266 +4,174 @@ if (exchangeRates || networkVolume)
- var utxoCalculatingDesc = "At startup the app pulls a summary of the UTXO set. Until this summary is retrieved this data can't be displayed. Wait for the summary request to your node to return, then refresh this page.";
div.row.index-summary
div(class=colClass)
h5.h6 Mining
table.table.table-borderless.table-sm.table-hover.mb-lg-0
tbody
if (hashrate1d && hashrate7d)
- var hashrate1dayData0 = utils.formatLargeNumber(hashrate1d, 0);
- var hashrate7dayData0 = utils.formatLargeNumber(hashrate7d, 0);
- var hashrate1dayData1 = utils.formatLargeNumber(hashrate1d, 1);
- var hashrate7dayData1 = utils.formatLargeNumber(hashrate7d, 1);
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-tachometer-alt.mr-1.summary-icon
span.border-dotted(title="Estimates for global network hashrate for 1 day / 7 days.", data-toggle="tooltip") Hashrate
small.ml-1 (d/w)
td.text-right.text-monospace
span.d-xxl-none #{hashrate1dayData0[0]}
span.d-none.d-xxl-inline #{hashrate1dayData1[0]}
small.text-muted /
span.d-xxl-none #{hashrate7dayData0[0]}
span.d-none.d-xxl-inline #{hashrate7dayData1[0]}
small.border-dotted(title=`${hashrate1dayData0[1].abbreviation}H = ${hashrate1dayData0[1].name}-hash (x10^${hashrate1dayData0[1].exponent})`, data-toggle="tooltip") #{hashrate1dayData0[1].abbreviation}H/s
if (!["regtest", "signet"].includes(global.activeBlockchain))
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-edit.mr-1.summary-icon
span.border-dotted(title="Estimate of the number of days the current global hashrate would require to produce all the hashes needed to re-write the entire blockchain.", data-toggle="tooltip") Chain Rewrite Days
td.text-right.text-monospace
- var globalHashCount = parseInt("0x" + getblockchaininfo.chainwork);
- var rewriteDays = globalHashCount / hashrate7d / 60 / 60 / 24;
span #{new Decimal(rewriteDays).toDecimalPlaces(1)}
if (exchangeRates)
+contentSection("Market")
- var colCount = 3 + (networkVolume ? 1 : 0) + (goldExchangeRates ? 1 : 0);
+summaryRow(colCount)
+summaryItem("Exchange Rate", `Source: ${new URL(coinConfig.exchangeRateData.jsonUrl).hostname}`)
span.text-success #{utils.formatValueInActiveCurrency(1.0)}
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-dumbbell.mr-1.summary-icon
span Difficulty
td.text-right.text-monospace
- var difficultyData = utils.formatLargeNumber(getblockchaininfo.difficulty, 2);
span.border-dotted(data-toggle="tooltip", title=parseFloat(getblockchaininfo.difficulty).toLocaleString())
span #{difficultyData[0]}
small.px-2.px-lg-0.px-xl-2 x
span 10
sup #{difficultyData[1].exponent}
+summaryItem("Sats Rate")
- var satsRateData = utils.satoshisPerUnitOfActiveCurrency();
span #{satsRateData.amt}
small #{satsRateData.unit}
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-unlock.mr-1.summary-icon
span Pending Tx
td.text-right.text-monospace
if (mempoolInfo)
- var colorClass = "text-success";
if (mempoolInfo.size > 7000)
- colorClass = "text-warning";
if (mempoolInfo.size > 11000)
- colorClass = "text-danger";
+summaryItem("Market Cap")
- var supply = utils.estimatedSupply(getblockchaininfo.blocks);
- var activeCurrency = global.currencyFormatType.length > 0 ? global.currencyFormatType : "usd";
- var xxx = utils.formatLargeNumber(parseFloat(supply) * exchangeRates[activeCurrency], 3);
span(class=colorClass) #{mempoolInfo.size.toLocaleString()}
if (activeCurrency == "eur")
span €
else
span $
else
span.text-danger.border-dotted(title="There was an error getting this data. Check your application logs for possible explanation.", data-toggle="tooltip") Error
span #{xxx[0]}
if (xxx[1].textDesc)
span #{xxx[1].textDesc}
else
span x 10
sup #{xxx[1].exponent}
// ["154.9",{"val":1000000000,"name":"giga","abbreviation":"G","exponent":"9"}]
if (smartFeeEstimates)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-bullseye.mr-1.summary-icon
span.border-dotted(title="Current fee estimates (using 'estimatesmartfee') for getting a transaction included in 1 block, 6 blocks (1 hr), 144 blocks (1 day), or 1,008 blocks (1 week).", data-toggle="tooltip") Fee Targets
if (false)
small.ml-1 (1/h/d/w)
td.text-right.text-monospace
if (smartFeeEstimates)
span #{smartFeeEstimates[1]}
small.d-md-none
small.text-muted /
span #{smartFeeEstimates[6]}
small.text-muted /
span #{smartFeeEstimates[144]}
small.text-muted /
span #{smartFeeEstimates[1008]}
small sat/vB
if (goldExchangeRates)
+summaryItem("Gold Rate", `Source: ${new URL(coinConfig.goldExchangeRateData.jsonUrl).hostname}`)
span.text-warning #{utils.formatValueInGoldOunces(1.0)}
small.ms-1.text-muted oz
if (networkVolume)
+summaryItem("24hr Volume", `Total output of all transactions over the last 24hrs (blocks: [#${networkVolume.d1.endBlock.toLocaleString()} - #${networkVolume.d1.startBlock.toLocaleString()}]).`)
- var currencyValue = parseInt(networkVolume.d1.amt);
- var currencyValueDecimals = 0;
include ./value-display.pug
.clearfix
.float-start
+sectionTitle("Network Summary")
.float-end
if (userSettings.homepageShowTechDetails == "true")
a.me-2(href="./changeSetting?name=homepageShowTechDetails&value=false", title="Hide technical network details", data-bs-toggle="tooltip", data-bs-placement="bottom")
i.fas.fa-server.text-muted
else
a.me-2(href="./changeSetting?name=homepageShowTechDetails&value=true", title="Show technical network details", data-bs-toggle="tooltip", data-bs-placement="bottom")
i.fas.fa-server.text-muted
+contentSection
+summaryRow(1 + (hashrate7d ? 1 : 0) + (chainTxStats[7 * targetBlocksPerDay] ? 1 : 0) + (difficultyAdjustmentData ? 1 : 0))
if (hashrate7d)
+summaryItem("7d Hashrate")
- var hashrate7dayData = utils.formatLargeNumber(hashrate7d, 1);
span.me-2 #{hashrate7dayData[0]}
span.border-dotted.me-2(title=`${hashrate7dayData[1].abbreviation}H = ${hashrate7dayData[1].name}-hash (x10^${hashrate7dayData[1].exponent})` data-bs-toggle='tooltip') #{hashrate7dayData[1].abbreviation}H/s
if (hashrate30d)
- var ratio = hashrate7d / hashrate30d;
small
| (
span.border-dotted(title='7d hashrate vs 30d hashrate.' data-bs-toggle='tooltip')
if (ratio > 1)
- var ratioPercent = new Decimal(ratio).times(100).minus(100);
span.text-success +#{ratioPercent.toDP(1)}%
else
span.text-danger.border-dotted(title="There was an error getting this data. Check your application logs for possible explanation.", data-toggle="tooltip") Error
- var ratioPercent = new Decimal(100).minus(new Decimal(ratio).times(100));
span.text-danger -#{ratioPercent.toDP(1)}%
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-clock.mr-1.summary-icon
span.border-dotted(title=`Average block time for blocks in the current difficulty epoch (#${difficultyPeriod}). The difference between the target (${coinConfig.targetBlockTimeMinutes}min) and this time indicates how the difficulty will adjust for the next epoch.`, data-toggle="tooltip") Block Time
small.ml-1 (e#{difficultyPeriod.toLocaleString()})
td.text-right.text-monospace
- var firstBlockHeader = difficultyPeriodFirstBlockHeader;
- var currentBlock = latestBlocks[0];
- var heightDiff = currentBlock.height - firstBlockHeader.height;
- var timeDiff = currentBlock.mediantime - firstBlockHeader.mediantime;
- var timePerBlock = timeDiff / heightDiff;
- var timePerBlockDuration = moment.duration(timePerBlock * 1000);
- var daysUntilAdjustment = new Decimal(blocksUntilDifficultyAdjustment).times(timePerBlock).dividedBy(60 * 60 * 24);
- var duaDP1 = daysUntilAdjustment.toDP(1);
- var daysUntilAdjustmentStr = daysUntilAdjustment > 1 ? `~${duaDP1} day${duaDP1 == "1" ? "" : "s"}` : "< 1 day"
| )
if (timePerBlock > 600)
- var diffAdjPercent = new Decimal(timeDiff / heightDiff / 600).times(100).minus(100);
- var diffAdjText = `Blocks during the current difficulty epoch have taken this long, on average, to be mined. If this pace continues, then in ${blocksUntilDifficultyAdjustment.toLocaleString()} block${blocksUntilDifficultyAdjustment == 1 ? "" : "s"} (${daysUntilAdjustmentStr}) the difficulty will adjust downward: -${diffAdjPercent.toDP(1)}%`;
- var diffAdjSign = "-";
else
- var diffAdjPercent = new Decimal(100).minus(new Decimal(timeDiff / heightDiff / 600).times(100));
- var diffAdjText = `Blocks during the current difficulty epoch have taken this long, on average, to be mined. If this pace continues, then in ${blocksUntilDifficultyAdjustment.toLocaleString()} block${blocksUntilDifficultyAdjustment == 1 ? "" : "s"} (${daysUntilAdjustmentStr}) the difficulty will adjust upward: +${diffAdjPercent.toDP(1)}%`;
- var diffAdjSign = "+";
if (chainTxStats[7 * targetBlocksPerDay])
+summaryItem("7d Transactions")
| #{chainTxStats[7 * targetBlocksPerDay].window_tx_count.toLocaleString()}
span.border-dotted(title=diffAdjText, data-toggle="tooltip") #{timePerBlockDuration.format()}
if (!isNaN(diffAdjPercent))
small.text-muted.ml-1 (#{diffAdjSign}#{diffAdjPercent.toDP(1)}%)
if (difficultyAdjustmentData)
- var desc = `Estimate for the difficulty adjustment that will occur in ${difficultyAdjustmentData.blocksLeft.toLocaleString()} block${difficultyAdjustmentData.blocksLeft == 1 ? "" : "s"} (${difficultyAdjustmentData.daysLeftStr}). This is calculated using the average block time over the last ${difficultyAdjustmentData.calculationBlockCount} block(s). This estimate becomes more reliable as the difficulty epoch nears its end.`;
+summaryItem("Difficulty Δ", desc)
span.me-2(class=(difficultyAdjustmentData.sign == "+" ? "text-success" : "text-danger")) #{difficultyAdjustmentData.sign}#{difficultyAdjustmentData.delta.toDP(2)}
small.text-muted
| (#{difficultyAdjustmentData.daysLeftStr})
div(class=colClass)
h5.h6 Blockchain
+summaryItem("Coins", utxoSetSummary ? null : `This value is estimated.`)
- var maxSupply = coinConfig.maxSupplyByNetwork[activeBlockchain];
table.table.table-borderless.table-sm.table-hover.mb-lg-0
tbody
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-sign-out-alt.mr-1.summary-icon
//span Total Transactions
span Total Txs
td.text-right.text-monospace
if (txStats && txStats.totalTxCount)
span #{txStats.totalTxCount.toLocaleString()}
else
span ???
if (utxoSetSummary)
span #{parseFloat(utxoSetSummary.total_amount).toLocaleString()}
small.ms-2.text-muted
| (
span.border-dotted(title=`${new Decimal(utxoSetSummary.total_amount).dividedBy(maxSupply).times(100).toDP(4)}% have been mined into circulation`, data-bs-toggle="tooltip") #{new Decimal(utxoSetSummary.total_amount).dividedBy(maxSupply).times(100).toDP(1)}%
| )
if (getblockchaininfo.size_on_disk)
- var sizeData = utils.formatLargeNumber(getblockchaininfo.size_on_disk, 2);
else
- var estimatedSupply = utils.estimatedSupply(getblockchaininfo.blocks);
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-database.mr-1.summary-icon
span Data Size
td.text-right.text-monospace #{sizeData[0]} #{sizeData[1].abbreviation}B
span #{parseFloat(estimatedSupply).toLocaleString()}
small.ms-2.text-muted
| (
span.border-dotted(title=`${estimatedSupply.dividedBy(maxSupply).times(100).toDP(4)}% have been mined into circulation`, data-bs-toggle="tooltip") #{estimatedSupply.dividedBy(maxSupply).times(100).toDP(1)}%
| )
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-bolt.mr-1.summary-icon
span.border-dotted(title="The total amount of work necessary to produce the active chain, approximated in 'hashes'.", data-toggle="tooltip") Chain Work
td.text-right.text-monospace
- var chainworkData = utils.formatLargeNumber(parseInt("0x" + getblockchaininfo.chainwork), 2);
span.border-dotted(data-toggle="tooltip", title=`hex: ${getblockchaininfo.chainwork.replace(/^0+/, '')}`)
span #{chainworkData[0]}
small.px-2.px-lg-0.px-xl-2 x
span 10
sup #{chainworkData[1].exponent}
if (false)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-arrow-circle-up.mr-1.summary-icon
span.border-dotted(title="The active 'soft' forks on the network.", data-toggle="tooltip") Soft-Forks
td.text-right.text-monospace.word-wrap
ul.list-inline.mb-0
each softforkData, softforkName in getblockchaininfo.softforks
li.list-inline-item
small.border-dotted(title=`${JSON.stringify(softforkData)}`, data-toggle="tooltip") #{softforkName}
hr.mt-4.mb-4
+summaryRow(4)
+summaryItem("Mempool")
span(class=(mempoolInfo.size > 11000) ? "text-danger" : (mempoolInfo.size > 7000 ? "text-warning" : "text-success")) #{mempoolInfo.size.toLocaleString()}
if (false)
+summaryItem("Next Block (est.)")
| #{new Decimal(nextBlockMinFeeRate).toDP(0)}
a(href=`./tx/${nextBlockMinFeeTxid}`)
i.fas.fa-circle
| - #{new Decimal(nextBlockMaxFeeRate).toDP(0)}
a(href=`./tx/${nextBlockMaxFeeTxid}`)
i.fas.fa-circle
small.ms-2.text-muted sat/vB
+summaryItem("Smart Fee (1 block)")
| #{smartFeeEstimates[1]}
small.ms-2.text-muted sat/vB
+summaryItem("Smart Fee (1 hr)")
| #{smartFeeEstimates[6]}
small.ms-2.text-muted sat/vB
+summaryItem("Smart Fee (1 day)")
| #{smartFeeEstimates[144]}
small.ms-2.text-muted sat/vB
if (userSettings.homepageShowTechDetails == "true")
hr.mt-4.mb-4
+summaryRow(1 + (getblockchaininfo.size_on_disk ? 1 : 0) + (utxoSetSummary || utxoSetSummaryPending ? 1 : 0))
if (getblockchaininfo.size_on_disk)
+summaryItem("Disk Usage")
- var sizeData = utils.formatLargeNumber(getblockchaininfo.size_on_disk, 1);
| #{sizeData[0]} #{sizeData[1].abbreviation}B
+summaryItem("Total Hashes", "Estimate of the total work necessary to produce the blockchain, measured in 'hashes'.")
- var chainworkData = utils.formatLargeNumber(parseInt("0x" + getblockchaininfo.chainwork), 2);
span #{chainworkData[0]}
small.px-2.px-lg-0.px-xl-2 x
span 10
sup #{chainworkData[1].exponent}
if (utxoSetSummary || utxoSetSummaryPending)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-grip-horizontal.mr-1.summary-icon
span.border-dotted(title="The number / data size of 'unspent transaction outputs' (UTXOs) in the blockchain.", data-toggle="tooltip") UTXO Set
td.text-right.text-monospace
if (utxoSetSummary)
- var utxoCount = utils.formatLargeNumber(utxoSetSummary.txouts, 2);
- var utxoDataSize = utils.formatLargeNumber(utxoSetSummary.disk_size, 2);
span #{utxoCount[0]} #{utxoCount[1].abbreviation}
small.text-muted /
span #{utxoDataSize[0]} #{utxoDataSize[1].abbreviation}B
else
small.text-muted.border-dotted(title=utxoCalculatingDesc, data-toggle="tooltip") calculating...
if (utxoSetSummary || utxoSetSummaryPending)
+summaryItem("UTXOs", "The total number of unspent transaction outputs (spendable coin units).")
if (utxoSetSummary)
- var utxoCount = utils.formatLargeNumber(utxoSetSummary.txouts, 2);
//| #{utxoCount[0]} #{utxoCount[1].abbreviation}
| #{utxoSetSummary.txouts.toLocaleString()}
else
small.text-muted.border-dotted(title=utxoCalculatingDesc, data-bs-toggle="tooltip") calculating...
if (utxoSetSummary || utxoSetSummaryPending)
tr
th.px-2.px-lg-0.px-xl-2
i.fab.fa-bitcoin.mr-1.summary-icon
span Total Supply
td.text-right.text-monospace
if (utxoSetSummary)
- var maxSupply = coinConfig.maxSupplyByNetwork[activeBlockchain];
span.border-dotted(title=`${new Decimal(utxoSetSummary.total_amount).dividedBy(maxSupply).times(100).toDP(4)}% produced`, data-toggle="tooltip") #{parseFloat(utxoSetSummary.total_amount).toLocaleString()}
else
small.text-muted.border-dotted(title=utxoCalculatingDesc, data-toggle="tooltip") calculating...
if (networkVolume || exchangeRates)
div(class=colClass)
h5.h6 Financials
table.table.table-borderless.table-sm.table-hover.mb-lg-0
tbody
if (exchangeRates)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-money-bill-wave-alt.mr-1.summary-icon
span.border-dotted(data-toggle="tooltip", title=`Exchange-rate data from: ${coinConfig.exchangeRateData.jsonUrl}`) Exchange Rate
td.text-right.text-monospace
span #{utils.formatValueInActiveCurrency(1.0)}
if (exchangeRates)
tr
th.px-2.px-lg-0.px-xl-2
i.fab.fa-btc.mr-1.summary-icon
span Sats Rate
td.text-right.text-monospace
- var satsRateData = utils.satoshisPerUnitOfActiveCurrency();
span #{satsRateData.amt}
small #{satsRateData.unit}
if (exchangeRates && goldExchangeRates)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-mountain.mr-1.summary-icon
span.border-dotted(data-toggle="tooltip", title=`Exchange-rate data from: ${coinConfig.goldExchangeRateData.jsonUrl}`) AU Exchange Rate
td.text-right.text-monospace
span #{utils.formatValueInGold(1.0)}
if (exchangeRates)
if (utxoSetSummary || utxoSetSummaryPending)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-globe.mr-1.summary-icon
span Market Cap
td.text-right.text-monospace
if (utxoSetSummary)
- var activeCurrency = global.currencyFormatType.length > 0 ? global.currencyFormatType : "usd";
- var xxx = utils.formatLargeNumber(parseFloat(utxoSetSummary.total_amount) * exchangeRates[activeCurrency], 1);
if (activeCurrency == "eur")
span €
else
span $
span #{xxx[0]}
if (xxx[1].textDesc)
span #{xxx[1].textDesc}
else
span x 10
sup #{xxx[1].exponent}
// ["154.9",{"val":1000000000,"name":"giga","abbreviation":"G","exponent":"9"}]
else
small.text-muted.border-dotted(title=utxoCalculatingDesc, data-toggle="tooltip") calculating...
if (networkVolume)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-history.mr-1.summary-icon
span.border-dotted(title=`Total output of all transactions over the last 24 hrs (blocks: [#${networkVolume.d1.endBlock.toLocaleString()} - #${networkVolume.d1.startBlock.toLocaleString()}]).`, data-toggle="tooltip") Volume
small.ml-1 (24h)
td.text-right.text-monospace
- var currencyValue = parseInt(networkVolume.d1.amt);
- var currencyValueDecimals = 0;
include ./value-display.pug

View file

@ -1,15 +1,7 @@
script(src="./js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
+graphPageScriptSetup
canvas.mb-3(id=graphData.id)
if (session.uiTheme && session.uiTheme == "dark")
script.
Chart.defaults.global.defaultFontColor='white';
else
script.
Chart.defaults.global.defaultFontColor='black';
script.
Chart.defaults.global.elements.point.radius = 1;
var ctx = document.getElementById("#{graphData.id}").getContext('2d');
@ -43,6 +35,9 @@ script.
display: true,
labelString: '#{graphData.xaxisTitle}'
},
gridLines: {
color: gridLineColor
},
ticks: {
stepSize: #{graphData.xaxisStep},
/*callback: function(value, index, values) {
@ -63,6 +58,9 @@ script.
display: true,
labelString: '#{graphData.yaxisTitle}'
},
gridLines: {
color: gridLineColor
},
ticks: {
callback: function(value, index, values) {
if (value > 1000000) {

View file

@ -19,10 +19,10 @@ div.modal.fade(id="pageErrorsModal" role="dialog" aria-hidden="true")
h6 Error ##{(itemIndex + 1).toLocaleString()}
hr
//pre
// code.json.bg-light #{JSON.stringify(item.error, null, 4)}
// code.json #{JSON.stringify(item.error, null, 4)}
pre
code.json.bg-light #{JSON.stringify(item, null, 4)}
code.json #{JSON.stringify(item, null, 4)}
if (item.error.userData)
div.mb-3
@ -30,14 +30,14 @@ div.modal.fade(id="pageErrorsModal" role="dialog" aria-hidden="true")
div.highlight
pre
code.json.bg-light #{JSON.stringify(item.error.userData, null, 4)}
code.json #{JSON.stringify(item.error.userData, null, 4)}
if (item.error.stack)
h6 Stacktrace
- var stackFirstNLines = item.error.stack.split("\n").slice(0, 7).join("\n");
div.highlight
pre
code.json.bg-light #{stackFirstNLines}
code.json #{stackFirstNLines}

View file

@ -1,26 +0,0 @@
- var pageNumbers = [];
- for (var x = 1; x <= pageCount; x++) {
- pageNumbers.push(x);
- }
nav(aria-label="Page navigation")
ul.pagination.pagination-lg.justify-content-center.flex-wrap
li.page-item(class=(pageNumber == 1 ? "disabled" : false))
a.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.page-item(class=(x == pageNumber ? "active" : false))
a.page-link(href=(paginationUrlFunction(x))) #{x}
if (x == 1 && pageNumber > 6)
li.page-item.disabled
a.page-link(href="javascript:void(0)") ...
else if (x == (pageCount - 1) && pageNumber < (pageCount - 5))
li.page-item.disabled
a.page-link(href="javascript:void(0)") ...
li.page-item(class=(pageNumber == pageCount ? "disabled" : false))
a.page-link(href=(pageNumber == pageCount ? "javascript:void(0)" : paginationUrlFunction(pageNumber + 1)), aria-label="Next")
span(aria-hidden="true") &raquo;

View file

@ -0,0 +1,425 @@
mixin pageTitle(text, subtext, copyableSubtext=false)
h1.fw-light.word-wrap.mb-4
| #{text}
br
span.text-tiny #{subtext}
if (copyableSubtext)
small
+copyTextButton(subtext)
//hr.mb-3
mixin sectionTitle(text, toggle=false, toggleUniqueClass, toggleUserSettingName, toggleOpen)
if (false)
pre
code.json #{JSON.stringify(session.userSettings)}
h3.h5.mb-1.fw-light(class=(toggle && !toggleOpen ? "mb-section" : false), class=(!toggle || toggleOpen ? "d-block" : "d-none"))
| #{text}
if (toggle)
small(title=`Toggle ${text}`, data-bs-toggle="tooltip")
a.text-muted.fs-6(href=`./changeSetting?name=${toggleUserSettingName}&value=false`)
i.toggle-plus-minus.far.ms-2(class=(toggleOpen ? "fa-minus-square" : "fa-plus-square"))
mixin contentSection(title, toggleable=false, toggleUserSettingName, defaultOpen=true, cardUi=true)
if (toggleable)
- var toggleUniqueClass = `section-${utils.getRandomString(10, "aA#")}`;
- var toggleOpen = userSettings[toggleUserSettingName] == null ? defaultOpen : (userSettings[toggleUserSettingName] == "true" || userSettings[toggleUserSettingName] == true);
if (title)
+sectionTitle(title, toggleable, toggleUniqueClass, toggleUserSettingName, toggleOpen)
.mb-section(class=toggleUniqueClass, style=(toggleable && !toggleOpen ? "display: none;" : false))
if (cardUi)
.card.mb-section.shadow-sm
.card-body
block
else
block
mixin summaryRow(itemCount)
- locals.summaryItemCount = itemCount;
- locals.summaryItemIndex = 0;
- locals.colCounts = {"sm": 1, "md": Math.min(itemCount, 3), "lg": Math.min(itemCount, 4), "xl": Math.min(itemCount, 5)};
- locals.rowCountsArray = utils.objectProperties(locals.colCounts).map(x => [x, locals.colCounts[x]]).map(x => [x[0], (Math.floor(itemCount / x[1]) + ((itemCount % x[1] > 0) ? 1 : 0))]);
- locals.rowCounts = {};
- locals.rowCountsArray.forEach(x => locals.rowCounts[x[0]] = x[1]);
//h1 #{itemCount} #{JSON.stringify(locals.rowCounts)}
.row.row-cols-1(class=utils.objectProperties(locals.colCounts).map(x => `row-cols-${x}-${locals.colCounts[x]}`).join(" "))
block
mixin summaryTitle(title, titleDesc, subtitle, subtitleDesc)
span.fs-6.text-uppercase.fw-light.text-muted(class=(titleDesc ? "border-dotted" : false), class=(subtitle ? "me-2" : false), title=titleDesc, data-bs-toggle="tooltip", data-bs-html="true") #{title}
if (subtitle)
small.text-muted
| (
span(class=(subtitleDesc ? "border-dotted" : false), title=subtitleDesc, data-bs-toggle="tooltip", data-bs-html="true") #{subtitle}
| )
mixin summaryItem(title, titleDesc, subtitle, subtitleDesc)
- var rowIndexes = utils.objectProperties(locals.colCounts).map(x => [x, locals.colCounts[x]]).map(x => [x[0], Math.floor(locals.summaryItemIndex / x[1])]);
.col(class=(locals.summaryItemIndex == (locals.summaryItemCount - 1) ? "mb-0" : "mb-3"), class=rowIndexes.map(x => `mb-${x[0]}-${(x[1] < (locals.rowCounts[x[0]] - 1) ? "4" : "0")}`))
//span.text-danger (#{JSON.stringify(locals.rowCounts)})
.text-start.text-md-center
+summaryTitle(title, titleDesc, subtitle, subtitleDesc)
.lead.text-start.text-md-center
block
- locals.summaryItemIndex++;
// options keys:
// - blockHeightsByTxid
// - sharedBlockHeight (int)
// - showDates (true|false)
// - highlightAddress (address)
// - addrGainsByTxid (map)
// - addrLossesByTxid (map)
// - mempoolDetailsByTxid (map)
// txBlockHeightsByTxid_orSharedBlockHeight, showDates=false, highlightAddress, addrGainsByTxid, addrLossesByTxid)
mixin txList(visibleTxs, txCount, limit, offset, inputsByTxid, options)
if (txCount > limit)
.rounded-0.rounded-top.bg-body.border
.p-3
h6.fw-normal.mb-2 #{offset.toLocaleString()} - #{Math.min(offset + limit - 1, txCount - 1).toLocaleString()} of #{txCount.toLocaleString()}
+pagination(limit, offset, null, txCount, paginationBaseUrl)
.border.border-2
each tx, txIndex in visibleTxs
- var txBlockHeight = null;
if (options.blockHeightsByTxid)
- txBlockHeight = options.blockHeightsByTxid[tx.txid];
else if (options.sharedBlockHeight != undefined)
- txBlockHeight = options.sharedBlockHeight;
- var tx_height_param = (!global.txindexAvailable && txBlockHeight) ? `@${txBlockHeight}` : '';
.border.bg-body.p-3(class=(txIndex == 0 && txCount <= limit ? "rounded-top" : false), class=(txCount <= limit && txIndex == (visibleTxs.length - 1) ? "rounded-bottom" : false))
if (tx && tx.txid)
.clearfix
.float-start
.mb-4.word-wrap
span.badge.bg-primary.fw-normal.me-2(title=`Index in Block: #${(txIndex + offset).toLocaleString()}`, data-bs-toggle="tooltip") ##{(txIndex + offset).toLocaleString()}
a(href=`./tx/${tx.txid}${tx_height_param}`) #{tx.txid}
if (global.specialTransactions && global.specialTransactions[tx.txid])
a.ms-2(data-bs-toggle="tooltip", title=`<b>${coinConfig.name} Fun!</b><br/>There's something special about this transaction...<br/>Click to read more.`, data-bs-html="true")
+funItemIcon
.float-end
if (options.mempoolDetailsByTxid && options.mempoolDetailsByTxid[tx.txid])
- var mempoolDetails = options.mempoolDetailsByTxid[tx.txid].entry;
- var feeRate = new Decimal(mempoolDetails.fee).times(coinConfig.baseCurrencyUnit.multiplier).dividedBy(mempoolDetails.vsize);
span.badge.bg-dark.ms-2
| #{feeRate.toDP(0)}
span.fw-light.ms-1 sat/vB
if (options.addrGainsByTxid && options.addrGainsByTxid[tx.txid])
- var currencyValue = options.addrGainsByTxid[tx.txid];
span.badge.bg-success.ms-2 +
include ./value-display.pug
if (options.addrLossesByTxid && options.addrLossesByTxid[tx.txid])
- var currencyValue = options.addrLossesByTxid[tx.txid];
span.badge.bg-danger.ms-2 -
include ./value-display.pug
if (options.showDates)
span.badge.bg-dark.ms-2
i.far.fa-clock.me-1
if (tx.time)
+timeAgo(tx.time, true)
span.ms-1 ago
else
| unconfirmed
div
- var txInputs = inputsByTxid[tx.txid];
- var blockHeight = txBlockHeight != undefined ? txBlockHeight : -1;
if (options.highlightAddress)
- var txIOHighlightAddress = options.highlightAddress;
include ./transaction-io-details.pug
if (txIndex < (visibleTxs.length - 1) || txCount > limit)
.border.border-2
if (txCount > limit)
.rounded-0.rounded-bottom.bg-body.border
.p-3
if (!crawlerBot && txCount > limit)
h6.fw-normal.mb-2 #{offset.toLocaleString()} - #{Math.min(offset + limit - 1, txCount - 1).toLocaleString()} of #{txCount.toLocaleString()}
+pagination(limit, offset, null, txCount, paginationBaseUrl)
mixin pageTabs(names)
ul.nav.nav-tabs
each name, nameIndex in names
li.nav-item(style=(nameIndex == 0 ? "margin-left: 10px;" : false))
a.nav-link(class=(nameIndex == 0 ? "active" : false), data-bs-toggle="tab", href=`#tab-${name.replace(" ", "-")}`, role="tab") #{name}
.bg-gradient-body-to-main.pb-4.mb-4
mixin pillTabs(names)
ul.nav.nav-pills.mb-3
each name, nameIndex in names
li.nav-item
a.nav-link(class=(nameIndex == 0 ? "active" : false), data-bs-toggle="tab", href=`#tab-${name.replace(" ", "-")}`, role="tab") #{name}
mixin pageTab(name, defaultActive=false)
div.tab-pane(id=`tab-${name.replace(" ", "-")}`, class=(defaultActive ? "active" : false), role="tabpanel")
block
mixin confirmations(confirmations, abbreviated=true)
if (confirmations < 6)
span.text-warning.border-dotted(title="Fewer than 6 confirmations is generally considered <b>unsettled</b> for high-value transactions because block <b>re-organizations</b> that could affect them have reasonable probability.", data-bs-toggle="tooltip", data-bs-html="true") #{confirmations.toLocaleString()}
i.fas.fa-unlock-alt.ms-1
else
span.border-dotted.text-success(title="6 confirmations is generally considered <b>settled</b>. High-value transactions may require more;<br/>Low-value transactions may require fewer.", data-bs-toggle="tooltip", data-bs-html="true")
| #{abbreviated && confirmations > 1000 ? `${Math.floor(confirmations/1000)}k` : confirmations.toLocaleString()}
i.fas.fa-check.ms-1
mixin timestamp(timestamp, includeAgo=true)
- var utcMoment = moment.utc(new Date(parseInt(timestamp) * 1000));
- var yearStr = utcMoment.format("Y");
- var nowYearStr = moment.utc(new Date()).format("Y");
- var dateStr = utcMoment.format("Y-M-D");
- var nowDateStr = moment.utc(new Date()).format("Y-M-D");
- var titleStr = `${utcMoment.format("Y-MM-DD HH:mm:ss")} utc`;
if (dateStr == nowDateStr)
span.border-dotted(title=titleStr, data-bs-toggle="tooltip")
| #{utcMoment.format("HH:mm")}
small.text-muted.ms-1 utc
else if (yearStr == nowYearStr)
span.border-dotted(title=titleStr, data-bs-toggle="tooltip")
| #{utcMoment.format("M/D HH:mm")}
small.text-muted.ms-1 utc
else
span.border-dotted(title=titleStr, data-bs-toggle="tooltip")
| #{utcMoment.format("YYYY-MM-DD")}
if (includeAgo)
small.text-muted.ms-2 (
+timeAgo(timestamp, true)
span ago)
mixin funAlert(html, text, referenceUrl)
.alert.alert-primary.shadow-sm.pb-0(role="alert", style="padding-left: 4rem;")
i.fas.fa-flag.fa-2x.fontawesome-shadow.text-warning.d-inline-block(style="position: absolute; top: 0; left: 0; width: 40px; margin-left: 0.8rem; margin-top: 1.2rem;")
h5.mb-2.fw-light #{coinConfig.name} Fun
if (html)
p
span !{html}
if (referenceUrl && referenceUrl.trim().length > 0 && html.indexOf(referenceUrl) == -1)
span &nbsp;
a(href=referenceUrl) Read more
else if (text)
p
span #{text}
if (referenceUrl && referenceUrl.trim().length > 0)
span &nbsp;
a(href=referenceUrl) Read more
else if (block)
p
block
mixin funItemIcon
i.fas.fa-flag.text-warning
mixin timeAgo(timeAgoTime, only1Element=false)
- var timeAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(timeAgoTime) * 1000))));
if (timeAgo.asHours() < 1)
if (timeAgo.asMinutes() < 1)
span #{timeAgo.seconds()}s
else
span #{timeAgo.minutes()}m
else
if (timeAgo.asHours() >= 1 && timeAgo.asHours() < 24)
span #{timeAgo.hours()}h
if (!only1Element && timeAgo.minutes() > 0)
span.ms-1 #{timeAgo.minutes()}m
else
if (only1Element)
span #{utils.shortenTimeDiff(timeAgo.format()).split(/,?\s+/)[0]}
else
span #{utils.shortenTimeDiff(timeAgo.format())}
mixin copyTextButton(text)
small.ms-2
if (false)
a.btn-clipboard(href="javascript:void(0)", title="Copy", data-clipboard-text=text, data-bs-toggle="tooltip", onclick=`copyTextToClipboard("${text}"); $(".icon-copy").toggle(); $(this).find(".icon-copied").toggle(); setTimeout(() => { $(this).find(".icon-copy").toggle(); $(this).find(".icon-copied").toggle(); }, 2000); return false;`)
i.far.fa-copy.text-info.icon-copy
i.fas.fa-check.text-success.icon-copied(style="display: none;")
a.btn-clipboard(href="javascript:void(0)", title="Copy", data-clipboard-text=text, data-bs-toggle="tooltip", onclick=`copyTextToClipboard("${text}"); $(this).attr("data-bs-original-title", "Copied!"); $(this).mouseleave(function() { $(this).tooltip("hide"); $(this).attr("data-bs-original-title", "Copy"); }); return false;`)
i.far.fa-copy.text-info
mixin dismissableInfoAlert(userSettingDismissedName, textToShowWhenClosed)
if (session.userSettings[userSettingDismissedName] != "true")
.alert.alert-primary.alert-dismissible.shadow-sm.mb-4(role="alert", style="padding-left: 4rem;")
i.fas.fa-info-circle.fa-2x.d-inline-block(style="position: absolute; top: 0; left: 0; width: 40px; margin-left: 0.8rem; margin-top: 1.2rem;")
block
a.btn-close(href=`./changeSetting?name=${userSettingDismissedName}&value=true`, aria-label="Close", style="text-decoration: none;", title="Hide this note", data-bs-toggle="tooltip")
//button.btn-close.text-muted(type="button", data-bs-dismiss="alert", aria-label="Close", title="Hide this note", data-bs-toggle="tooltip")
else if (textToShowWhenClosed)
.alert.alert-primary.shadow-sm.mb-4(role="alert")
.clearfix
.float-start
i.fas.fa-info-circle.fa-lg.me-3
| #{textToShowWhenClosed}
.float-end
a.text-muted(href=`./changeSetting?name=${userSettingDismissedName}&value=false`, aria-label="Close", style="text-decoration: none;", title="Open this note", data-bs-toggle="tooltip")
i.far.fa-plus-square.fa-lg
mixin warningAlert
.alert.alert-warning.shadow-sm.mb-4(role="alert", style="padding-left: 4rem;")
i.fas.fa-exclamation-triangle.fa-2x.d-inline-block(style="position: absolute; top: 0; left: 0; width: 40px; margin-left: 0.8rem; margin-top: 1.2rem;")
block
mixin blockRangeFilters
.clearfix
.float-start.me-4
div(id="time-range-buttons")
div.mb-2
span.border-dotted(title="Analyze blocks over the last N days (approximated using average block time).", data-bs-toggle="tooltip") Time Range
div.btn-group
a.btn.btn-primary.block-count-btn(href="javascript:void(0)", data-blockCount="144") 1 day
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="432") 3 day
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="1008") 7 day
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="4320") 30 day
.float-start.me-4
div(id="block-selections-buttons")
div.mb-2
span.border-dotted(title="Choose from a list of pre-configured 'interesting' historical block ranges.", data-bs-toggle="tooltip") Interesting History
div.dropdown
button.btn.btn-outline-primary.dropdown-toggle(type="button", id="preconfigured-dropdown", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false") Selections
div.dropdown-menu(aria-labelledby="preconfigured-dropdown")
a.dropdown-item(href="javascript:void(0)", data-blocks="0-199") First 200 Blocks
a.dropdown-item(href="javascript:void(0)", data-blocks="209900-210100") First Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="419900-420100") Second Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="629900-630100") Third Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="481724-481924") SegWit Activation ±100
.float-start
div(id="time-range-buttons")
div.mb-2
span.border-dotted(title="Manually enter a range of blocks to analyze.", data-bs-toggle="tooltip") Custom Block Range
form.form-inline.me-3(id="custom-range-form")
div.input-group
input.form-control(type="text", id="custom-range-start", placeholder="min height", style="width: 125px;")
input.form-control(type="text", id="custom-range-end", placeholder="max height", style="width: 125px;")
button.btn.btn-primary(type="submit") Go
mixin pagination(limit, offset, sort, itemCount, baseUrl, justification="start", large=false)
- var pageNumber = offset / limit + 1;
- var pageCount = Math.floor(itemCount / limit);
- if (pageCount * limit < itemCount) {
- pageCount++;
- }
- var paginationUrlFunction = function(x) {
- return `${baseUrl}?limit=${limit}&offset=${(x - 1) * limit}${sort ? ("&sort=" + sort) : ""}`
//- return baseUrl + "?limit=" + limit + "&offset=" + ((x - 1) * limit);
//- return baseUrl + "?limit=" + limit + "&offset=" + ((x - 1) * limit + "&sort=" + sort);
- }
- var pageNumbers = [];
- for (var x = 1; x <= pageCount; x++) {
- pageNumbers.push(x);
- }
nav(aria-label="Page navigation")
ul.pagination.flex-wrap.mb-0(class=`justify-content-${justification}`, class=(large ? "pagination-lg" : false))
li.page-item(class=(pageNumber == 1 ? "disabled" : false))
a.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.page-item(class=(x == pageNumber ? "active" : false))
a.page-link(href=(paginationUrlFunction(x))) #{x}
if (x == 1 && pageNumber > 6)
li.page-item.disabled
a.page-link(href="javascript:void(0)") ...
else if (x == (pageCount - 1) && pageNumber < (pageCount - 5))
li.page-item.disabled
a.page-link(href="javascript:void(0)") ...
li.page-item(class=(pageNumber == pageCount ? "disabled" : false))
a.page-link(href=(pageNumber == pageCount ? "javascript:void(0)" : paginationUrlFunction(pageNumber + 1)), aria-label="Next")
span(aria-hidden="true") &raquo;
mixin graphPageScriptSetup
script(src="./js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
if (userSettings.uiTheme && userSettings.uiTheme == "dark")
script.
Chart.defaults.global.defaultFontColor='white';
var gridLineColor = "#1a4280";
else
script.
Chart.defaults.global.defaultFontColor='black';
var gridLineColor = "#dddddd";

View file

@ -1,5 +0,0 @@
- var timeAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(timeAgoTime) * 1000))));
span
span(data-bs-toggle="tooltip", title=`${timeAgo.format()} ago`)
i.far.fa-clock

View file

@ -1,14 +0,0 @@
- var yearStr = moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("Y");
- var nowYearStr = moment.utc(new Date()).format("Y");
- var dateStr = moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("Y-M-D");
- var nowDateStr = moment.utc(new Date()).format("Y-M-D");
if (dateStr == nowDateStr)
else
span #{moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("M/D")}
if (yearStr != nowYearStr)
span , #{yearStr}
span
span.border-dotted(title=`${moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("Y-MM-DD HH:mm:ss")}`, data-bs-toggle="tooltip") #{moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("HH:mm")}

View file

@ -48,45 +48,68 @@ if (false)
if (true)
div
- var priorityList = config.site.prioritizedToolIdsList;
- var indexLists2Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 2);
- var indexLists3Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 3);
- var indexLists4Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 4);
- var indexLists5Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 5);
- var indexLists1Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 1);
- var indexLists2Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 2);
- var indexLists3Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 3);
// xs, sm, xxl
div.d-block.d-md-none.d-xxl-block(id="tools-2-col")
div.row
each indexList in indexLists2Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
// xs
div.d-block.d-sm-none(id="tools-1-col")
div.row
each indexList in indexLists1Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
// md
div.d-none.d-md-block.d-lg-none(id="tools-3-col")
div.row
each indexList in indexLists3Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
div.float-start
div
a(href=item.url) #{item.name}
// lg
div.d-none.d-lg-block.d-xl-none(id="tools-4-col")
div.row
each indexList in indexLists4Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
div(style="padding-left: 39px;")
p #{item.desc}
// xl
div.d-none.d-xl-block.d-xxl-none(id="tools-5-col")
div.row
each indexList in indexLists5Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
// sm, md, lg
div.d-none.d-sm-block.d-xl-none(id="tools-2-col")
div.row
each indexList in indexLists2Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-start
div
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
// xl, xxl
div.d-none.d-xl-block(id="tools-3-col")
div.row
each indexList in indexLists3Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-start
div
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}

View file

@ -1,10 +1,21 @@
- var fontawesomeInputName = "sign-in-alt";
- var fontawesomeOutputName = "sign-out-alt";
- var totalIOValues = utils.getTxTotalInputOutputValues(tx, txInputs, blockHeight);
mixin asciiHexDataDisplay(dataName, uniqueId, hex)
div
div.d-inline-block.small.border.rounded.p-1.px-2.card-highlight(style="max-width: 90%;")
span.fw-bold.border-dotted.me-2(title=`ASCII-encoded ${dataName}`, data-bs-toggle="tooltip") Ascii
span.font-plaintext #{utils.hex2ascii(hex)}
a.text-muted.ms-2(href="javascript:void(0)", onclick=`$('#hex-${uniqueId}').toggle(); $(this).find(".toggle-plus-minus").toggleClass("fa-plus-square"); $(this).find(".toggle-plus-minus").toggleClass("fa-minus-square"); return false;`, title="Toggle Hex Display", data-bs-toggle="tooltip")
i.toggle-plus-minus.far.fa-plus-square
div(id=`hex-${uniqueId}`, style="display: none;")
div.small.border.rounded.p-1.px-2.mt-2.word-wrap.card-highlight
span.fw-bold.border-dotted.me-2(title=`Hex-encoded ${dataName}`, data-bs-toggle="tooltip") Hex
span.font-plaintext #{hex}
div.row.font-monospace
div.col-lg-6
div.col-lg-6(class=(tx.vin.length >= tx.vout.length ? "border-lg-end" : false))
if (txInputs || !global.txindexAvailable)
- var extraInputCount = 0;
each txVin, txVinIndex in tx.vin
@ -21,14 +32,14 @@ div.row.font-monospace
if (txVin.coinbase || vout || !global.txindexAvailable)
div.clearfix
div.tx-io-label
a(data-bs-toggle="tooltip", title=`Input #${txVinIndex.toLocaleString()}`, style="white-space: nowrap;")
span.fw-bold &gt;&nbsp;
small.d-inline.d-md-none Input&nbsp;
a.badge.fw-normal.card-highlight.border.text-decoration-none(data-bs-toggle="tooltip", title=`Input #${txVinIndex.toLocaleString()}`, style="white-space: nowrap;")
span.me-tiny &gt;
small.d-inline.d-md-none.me-1 Input
if (tx.vin.length > 999)
small.word-wrap ##{txVinIndex}
small.word-wrap #{txVinIndex}
else
span ##{txVinIndex.toLocaleString()}
span #{txVinIndex.toLocaleString()}
if (tx.vin.length > 0)
small.d-inline.d-md-none.fw-light.text-muted / #{(tx.vin.length - 1).toLocaleString()}
@ -37,20 +48,9 @@ div.row.font-monospace
div.clearfix
div.tx-io-desc
if (txVin.coinbase)
span.badge.bg-success.me-2(title="Coinbase transactions are special transactions created by the miner of a block. They distribute the block reward (subsidy) as the miner chooses, along with all of the fees paid by transactions included in the block.", data-bs-toggle="tooltip") coinbase
span.badge.bg-success.me-2.mb-2(title="Coinbase transactions are special transactions created by the miner of a block. They distribute the block reward (subsidy) as the miner chooses, along with all of the fees paid by transactions included in the block.", data-bs-toggle="tooltip") coinbase
div.pt-1
small.fw-bold(tistle="ASCII encoded", data-bs-toggle="tooltip") data
small (ascii) -&nbsp;
small #{utils.hex2ascii(txVin.coinbase)}
br
small
a(href="javascript:void(0)" onclick="$('#coinbase-data-hex').show(); $(this).hide(); return false;") show raw
div(id="coinbase-data-hex", style="display: none;")
small.fw-bold data
small (hex) -&nbsp;
small.word-wrap #{txVin.coinbase}
+asciiHexDataDisplay("coinbase data", "coinbase-data", txVin.coinbase)
else
@ -69,24 +69,28 @@ div.row.font-monospace
if (deltaTYears > 5)
span.me-2(title=`This UTXO was very old! It existed for ${parseInt(deltaTYears)}+ years - from ${moment.utc(new Date(vout.utxoTime * 1000)).format('MMM D, Y')} until ${moment.utc(new Date(tx.time * 1000)).format('MMM D, Y')} - before being spent (destroyed) in this transaction!`, data-bs-toggle="tooltip")
i.fas.fa-certificate.text-primary
+funItemIcon
a(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsize(txInput.txid, 26)}
a(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsizeMiddle(txInput.txid, 22)}
span ##{txVin.vout}
if (vout && vout.scriptPubKey && vout.scriptPubKey.addresses)
div.word-wrap
small.text-muted
span.me-1 (addr:
a(href=`./address/${vout.scriptPubKey.addresses[0]}`, class=(txIOHighlightAddress == vout.scriptPubKey.addresses[0] ? "fw-bold" : null)) #{vout.scriptPubKey.addresses[0]}
a(href=`./address/${vout.scriptPubKey.addresses[0]}`, class=(txIOHighlightAddress == vout.scriptPubKey.addresses[0] ? "text-info" : null)) #{vout.scriptPubKey.addresses[0]}
if (txIOHighlightAddress == vout.scriptPubKey.addresses[0])
span(title="Current address", data-bs-toggle="tooltip")
i.fas.fa-arrow-left.text-warning.ms-1.text-info
if (global.specialAddresses[vout.scriptPubKey.addresses[0]])
- var specialAddressInfo = global.specialAddresses[vout.scriptPubKey.addresses[0]];
if (specialAddressInfo.type == "minerPayout")
span
a(data-bs-toggle="tooltip", title=`Miner payout address: ${specialAddressInfo.minerInfo.name}`)
i.fas.fa-certificate.text-primary
+funItemIcon
span )
@ -100,15 +104,19 @@ div.row.font-monospace
div.tx-io-value
div.tx-io-value.mt-3.mt-md-0
if (txVin.coinbase)
- var currencyValue = coinConfig.blockRewardFunction(blockHeight, activeBlockchain);
span.border-dotted(title="This is the <b>block subsidy</b> - newly created coins awarded to the miner that found this block.", data-bs-toggle="tooltip", data-bs-html="true", class=(tx.vin.length == 1 ? "fw-bold" : false))
span.border-dotted.me-1(title="This is the <b>block subsidy</b> - newly created coins awarded (automatically, by the network protocol) to the miner that found this block.", data-bs-toggle="tooltip", data-bs-html="true")
i.fas.fa-asterisk.text-success
span(class=(tx.vin.length == 1 ? "fw-bold" : false))
- var currencyValue = coinConfig.blockRewardFunction(blockHeight, activeBlockchain);
include ./value-display.pug
else
if (vout && vout.value)
- var currencyValue = vout.value;
span(class=(tx.vin.length == 1 ? "fw-bold" : false))
span(class=(tx.vin.length == 1 ? "fw-bold" : "fw-light"))
include ./value-display.pug
else if (!global.txindexAvailable)
@ -124,10 +132,10 @@ div.row.font-monospace
if (extraInputCount > 0)
div.clearfix
div.tx-io-label
a(data-bs-toggle="tooltip", title=`Input #${(tx.vin.length - extraInputCount + 1).toLocaleString()} - ${tx.vin.length.toLocaleString()}`, style="white-space: nowrap;")
span.fw-bold &gt;&nbsp;
span.d-inline.d-md-none Input&nbsp;
span #
a.badge.fw-normal.card-highlight.border.text-decoration-none(data-bs-toggle="tooltip", title=`Input #${(tx.vin.length - extraInputCount + 1).toLocaleString()} - ${tx.vin.length.toLocaleString()}`, style="white-space: nowrap;")
span.me-1 &gt;
span.d-inline.d-md-none.me-1 Input
span …
div.tx-io-content
div.clearfix
@ -140,27 +148,33 @@ div.row.font-monospace
a(href=`./tx/${tx.txid}`) transaction details
span )
div.tx-io-value
div.tx-io-value.mt-3.mt-md-0
- var currencyValue = new Decimal(totalIOValues.output).minus(new Decimal(totalIOValues.input));
include ./value-display.pug
hr
if (totalIOValues.input && totalIOValues.input > 0 && tx.vin.length > 1)
div.row.mb-5.mb-lg-0
div.row.mb-3.mb-md-0
div.col
div.fw-bold.text-start.text-md-end
div.text-start.text-md-end
if (totalIOValues.input > 0)
span.d-block.d-md-none Total Input:
- var currencyValue = totalIOValues.input;
span.fw-bold
include ./value-display.pug
div.col-lg-6
div.d-lg-none
div.my-4
hr
div.col-lg-6(class=(tx.vout.length > tx.vin.length ? "border-lg-start" : false))
- var maxRegularRowCount = (txIOHighlightAddress != null ? config.site.addressPage.txOutputMaxDefaultDisplay : 10000000);
- var regularRowCount = 0;
- var hiddenRowCount = 0;
- var nonZeroVoutValueCount = tx.vout.filter(vout => { return vout.value > 0; }).length;
each vout, voutIndex in tx.vout
- var highlightRow = false;
if (txIOHighlightAddress != null && vout.scriptPubKey && vout.scriptPubKey.addresses && vout.scriptPubKey.addresses[0] == txIOHighlightAddress)
@ -175,16 +189,16 @@ div.row.font-monospace
- hiddenRowCount++;
div(data-txid=tx.txid, class=(hiddenRow ? "d-none" : ""))
div.clearfix
.clearfix
div.tx-io-label
a(data-bs-toggle="tooltip", title=`Output #${voutIndex.toLocaleString()}`, style="white-space: nowrap;")
span.fw-bold &lt;&nbsp;
small.d-inline.d-md-none Output&nbsp;
a.badge.card-highlight.border.text-decoration-none.fw-normal(data-bs-toggle="tooltip", title=`Output #${voutIndex.toLocaleString()}`, style="white-space: nowrap;")
span.me-tiny &lt;
small.d-inline.d-md-none.me-1 Output
if (tx.vout.length > 999)
small.word-wrap ##{voutIndex}
small.word-wrap #{voutIndex}
else
span ##{voutIndex.toLocaleString()}
span #{voutIndex.toLocaleString()}
if (tx.vout.length > 0)
small.d-inline.d-md-none.fw-light.text-muted / #{(tx.vout.length - 1).toLocaleString()}
@ -195,89 +209,76 @@ div.row.font-monospace
if (vout.scriptPubKey)
if (vout.scriptPubKey.addresses)
if (true)
div.mb-tiny
div.mb-1
span.badge.bg-dark
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}
each addr in vout.scriptPubKey.addresses
a(id=`output-${voutIndex}`, href=`./address/${addr}`)
span.font-monospace.word-wrap(class=(highlightRow ? "fw-bold" : "")) #{addr}
span.font-monospace.word-wrap(class=(highlightRow ? "text-info" : null)) #{addr}
if (highlightRow)
span(title="Current address", data-bs-toggle="tooltip")
i.fas.fa-arrow-left.ms-1.text-warning
if (global.specialAddresses[addr])
- var specialAddressInfo = global.specialAddresses[addr];
if (specialAddressInfo.type == "minerPayout")
span
a(data-bs-toggle="tooltip", title=`Miner payout address: ${specialAddressInfo.minerInfo.name}`)
i.fas.fa-certificate.text-primary
+funItemIcon
br
else if (vout.scriptPubKey.hex && vout.scriptPubKey.hex.startsWith('6a24aa21a9ed'))
div.mb-tiny
span.badge.bg-primary OP_RETURN
div
small.text-muted (SegWit committment -&nbsp;
a(href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#commitment-structure", data-bs-toggle="tooltip", title="View developer docs", target="_blank") about
span )
div.mb-2
span.badge.bg-primary.me-2 OP_RETURN
span.badge.bg-dark.me-2 SegWit commitment
a(href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#commitment-structure", data-bs-toggle="tooltip", title="Read about the SegWit Commitment", target="_blank")
i.fas.fa-external-link-alt
br
small
a(href="javascript:void(0)" onclick=`$('#op-return-hex-${tx.txid}-${vout.n}').show(); $(this).hide(); return false;`) show raw
div(id=`op-return-hex-${tx.txid}-${vout.n}`, style="display: none;")
small.fw-bold data
small (hex) -&nbsp;
small.word-wrap #{vout.scriptPubKey.asm}
+asciiHexDataDisplay("OP_RETURN data", `op_return-${tx.txid}-${vout.n}`, vout.scriptPubKey.asm.substring("OP_RETURN ".length))
else if (vout.scriptPubKey.asm && vout.scriptPubKey.asm.startsWith('OP_RETURN '))
div.mb-tiny
div.mb-2
span.badge.bg-primary OP_RETURN
+asciiHexDataDisplay("OP_RETURN data", `op_return-${tx.txid}-${vout.n}`, vout.scriptPubKey.asm.substring("OP_RETURN ".length))
div
small.fw-bold data
small (ascii) -&nbsp;
small #{utils.hex2ascii(vout.scriptPubKey.asm.substring("OP_RETURN ".length))}
br
small
a(href="javascript:void(0)" onclick=`$('#op-return-hex-${tx.txid}-${vout.n}').show(); $(this).hide(); return false;`) show raw
div(id=`op-return-hex-${tx.txid}-${vout.n}`, style="display: none;")
small.fw-bold data
small (hex) -&nbsp;
small.word-wrap #{vout.scriptPubKey.asm}
else
div.mb-tiny
span.badge.bg-dark
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}
div
small.fw-bold asm
br
small.word-wrap #{vout.scriptPubKey.asm}
div
small.fw-bold hex
br
small.word-wrap #{vout.scriptPubKey.hex}
div.tx-io-value
if (utxos)
if (utxos[voutIndex])
i.fas.fa-lock.text-success.me-2(title="Unspent output." data-bs-toggle="tooltip")
else if (utxos[voutIndex] == null)
i.fas.fa-lock-open.text-secondary.me-2(title="Spent output." data-bs-toggle="tooltip")
- var currencyValue = vout.value;
span(class=(tx.vout.length == 1 ? "fw-bold" : false))
include ./value-display.pug
+asciiHexDataDisplay("ASM data", `asm-${tx.txid}-${vout.n}`, vout.scriptPubKey.asm)
if (tx.vout.length > 1)
div.tx-io-value.mt-3.mt-md-0
if (!vout.scriptPubKey.asm.startsWith('OP_RETURN '))
if (utxos)
if (utxos[voutIndex])
span(title="This output remains unspent (a valid UTXO)." data-bs-toggle="tooltip")
i.fas.fa-lock.text-success.me-2
else if (utxos[voutIndex] == null)
span(title="This output has been spent (destroyed)." data-bs-toggle="tooltip")
i.fas.fa-lock-open.text-secondary.me-2
if (vout.value > 0 && vout.scriptPubKey.asm.startsWith('OP_RETURN '))
span(title="Burned output - as an OP_RETURN output, this value is provably unspendable." data-bs-toggle="tooltip")
i.fas.fa-fire.text-danger.me-2
if (vout.value > 0)
- var currencyValue = vout.value;
span(class=(nonZeroVoutValueCount == 1 ? "fw-bold" : "fw-light"))
include ./value-display.pug
if (voutIndex < (tx.vout.length - 1) || nonZeroVoutValueCount > 1)
hr
else
@ -286,12 +287,14 @@ div.row.font-monospace
if (hiddenRowCount > 0)
a(href="javascript:void(0)", onclick=("showAllTxOutputs(this, '" + tx.txid + "');")) Show all #{tx.vout.length.toLocaleString()} outputs
if (tx.vout.length > 1)
if (nonZeroVoutValueCount > 1)
div.row.mb-0.mb-lg-0
div.col
div.fw-bold.text-start.text-md-end
div.text-start.text-md-end
span.d-block.d-md-none Total Output:
- var currencyValue = totalIOValues.output;
include ./value-display.pug
span.fw-bold
- var currencyValue = totalIOValues.output;
include ./value-display.pug

View file

@ -1,7 +1,7 @@
extends layout
block headContent
title Home
title Bitcoin Explorer
block content
if (getblockchaininfo == null)
@ -10,55 +10,53 @@ block content
ul
li
p
strong If you just started your node, it may still be initializing.
b If you just started your node, it may still be initializing.
div At startup, bitcoind verifies its block index before making itself available to serve data. The amount of time the verification process takes depends largely on the speed of the machine bitcoind is running on. If you're running bitcoind on low powered, inexpensive hardware, don't be surprised if this process takes a long time.
li
p
strong If you believe your bitcoind node is already initialized...
b If you believe your bitcoind node is already initialized...
div Check your RPC connection info and/or check this application's error log for possible hints about the cause of this issue.
else
if (config.demoSite && session.hideHomepageBanner != "true")
div.alert.alert-primary.alert-dismissible.shadow-sm.mb-3(role="alert")
span
span.fw-bold #{coinConfig.siteTitlesByNetwork[activeBlockchain]}
span is
a(href="https://github.com/janoside/btc-rpc-explorer", target="_blank") open-source
span and easy to set up. It can communicate with your
a(href=coinConfig.nodeUrl, target="_blank") #{coinConfig.name} Full Node
span via RPC. See the
a(href="https://github.com/janoside/btc-rpc-explorer", target="_blank") project description
span for a list of features and instructions for running.
+dismissableInfoAlert("homepageWelcomeBannerDismissed")
.clearfix
.float-start
h4.fw-light.mb-2 Welcome to Bitcoin Explorer!
.float-start
if (config.demoSite)
span.badge.bg-primary.mb-2.ms-3.mt-1 Public Demo
.mb-1 A beautiful, easy-to-use, educational explorer whose <u>only dependency is your <b>Bitcoin Core</b> node</u>!
ul.mb-2
li Open-Source
li Easy to setup / configure
li Designed to work well on low-powered devices (e.g. Raspberry Pi)
li Available for self-installation, or via several readymade-node integrations
.mb-2 Check out the code, support development with a donation, and follow the project on Twitter below.
.mt-2
if (global.sourcecodeProjectMetadata)
div.mt-2
a.btn.btn-primary.btn-sm.me-3.mb-1(href="https://github.com/janoside/btc-rpc-explorer")
i.fas.fa-star.me-2
span.me-2 Star
span.badge.bg-white.text-dark #{global.sourcecodeProjectMetadata.stargazers_count}
a.btn.btn-primary.text-white.me-2.mb-1(href="https://github.com/janoside/btc-rpc-explorer", title="View source code on Github", data-bs-toggle="tooltip")
i.fab.fa-github.fontawesome-shadow-dark.fa-lg.me-2
| View Source
a.btn.btn-primary.btn-sm.me-3.mb-1(href="https://github.com/janoside/btc-rpc-explorer/fork")
i.fas.fa-code-branch.me-2
span.me-2 Fork
span.badge.bg-white.text-dark #{global.sourcecodeProjectMetadata.forks_count}
if (!crawlerBot)
button.btn.btn-primary.btn-sm.mb-1(type="button", data-bs-toggle="modal", data-target="#exampleModalCenter")
i.fas.fa-heart.me-2
span Support Project
a.btn.btn-primary.me-2.mb-1(href="https://donate.btc21.org", target="_blank", title="Support this project with a donation", data-bs-toggle="tooltip")
i.fas.fa-heart.fontawesome-shadow-dark.text-danger.fa-lg.me-2
| Support Project
if (false)
div.mt-2
a.me-2(href="https://www.npmjs.com/package/btc-rpc-explorer" rel="nofollow" target="_blank")
img(src="https://camo.githubusercontent.com/a2a28dff9062102df7988314d014e68d4612bd83/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6274632d7270632d6578706c6f7265722e7376673f7374796c653d666c6174" alt="npm version" data-canonical-src="https://img.shields.io/npm/v/btc-rpc-explorer.svg?style=flat" style="max-width:100%;")
a(href="https://npmcharts.com/compare/btc-rpc-explorer?minimal=true" rel="nofollow" target="_blank")
img(src="https://camo.githubusercontent.com/07914c9a0c2f22f7493952dc969c57164bb14f77/687474703a2f2f696d672e736869656c64732e696f2f6e706d2f646d2f6274632d7270632d6578706c6f7265722e7376673f7374796c653d666c6174" alt="NPM downloads" data-canonical-src="http://img.shields.io/npm/dm/btc-rpc-explorer.svg?style=flat" style="max-width:100%;")
a.btn.btn-primary.me-2.mb-1(href="https://twitter.com/BitcoinExplorer", target="_blank", title="Follow this project on Twitter", data-bs-toggle="tooltip")
i.fab.fa-twitter.text-twitter.fontawesome-shadow-dark.fa-lg.me-2
| @BitcoinExplorer
if (config.demoSite)
.mt-2
i.fas.fa-asterisk.fontawesome-shadow.text-warning.me-2
| This is a public demo, but this tool is designed to be run by <b>all</b> Bitcoiners... <i>Don't Trust, Verify</i>!
a.close(href="./changeSetting?name=hideHomepageBanner&value=true", aria-label="Close", style="text-decoration: none;")
span(aria-hidden="true") &times;
if (getblockchaininfo.initialblockdownload && !["regtest", "signet"].includes(global.activeBlockchain))
div.alert.alert-warning.shadow-sm.border.mb-3
@ -66,117 +64,31 @@ block content
div.mb-1 Your node is currently downloading and verifying blockchain data. Until the process is finished, some features of this tool will be unusable and/or unreliable.
span.font-weight-bold Progress:
span.text-monospace #{new Decimal(getblockchaininfo.verificationprogress).times(100).toDP(3)}%
span.fw-bold Progress:
span.font-monospace #{new Decimal(getblockchaininfo.verificationprogress).times(100).toDP(3)}%
div.row
- var summaryColCount = 8;
if (exchangeRates)
- summaryColCount = 9;
div.mb-3(class=`col-xxl-${summaryColCount}`)
div.card.shadow-sm(style="height: 100%;")
div.card-body.px-2.px-sm-3
h3.h6.mb-0 Network Summary
hr
include includes/index-network-summary.pug
div.mb-3(class=`col-xxl-${12 - summaryColCount}`)
div.card.shadow-sm(style="height: 100%;")
div.card-body.px-2.px-sm-3
div.row
div.col
h3.h6.mb-0 Tools
div.col.text-right
a(href="./tools") More info &raquo;
hr
include includes/tools-card.pug
include includes/index-network-summary.pug
if (latestBlocks)
div.row.mb-3
div.col
div.card.shadow-sm
div.card-body.px-2.px-sm-3
div.row
div.col
h3.h6.mb-0 Latest Blocks
div.col.text-right
a(href="./blocks") See more &raquo;
+contentSection("Latest Blocks")
- var blocks = latestBlocks;
- var blockOffset = 0;
- var hideBlockIndex = true;
hr
include includes/blocks-list.pug
- var blocks = latestBlocks;
- var blockOffset = 0;
.text-center
.border.border-2.mt-2.mb-3
include includes/blocks-list.pug
a(href="./blocks") Browse Blocks &raquo;
if (false)
div.card.shadow-sm.mb-3
div.card-header
div.row
div.col
h3.h6.mb-0 Latest Blocks
if (getblockchaininfo.initialblockdownload)
small (#{(getblockchaininfo.headers - getblockchaininfo.blocks).toLocaleString()} behind)
div.col
span(style="float: right;")
a(href="./blocks")
span Browse Blocks &raquo;
+contentSection("Tools")
include includes/tools-card.pug
div.card-body
- var blocks = latestBlocks;
- var blockOffset = 0;
include includes/blocks-list.pug
if (txStats)
div.row.mb-3
div.col
div.card.shadow-sm
div.card-body.px-2.px-sm-3
div.row
div.col
h3.h6.mb-0 Transaction Stats
div.col.text-end
a(href="./tx-stats") See more &raquo;
hr
div.row
div.col-lg-6
div.table-responsive
table.table.text-end.mb-3.mb-lg-0
thead
tr
th Period
th Transactions
th Transactions Per Sec
tbody
each item, index in chainTxStatsIntervals
tr.font-monospace
td #{item[1]}
td #{chainTxStats[item[0]].window_tx_count.toLocaleString()}
td #{new Decimal(chainTxStats[item[0]].txrate).toDecimalPlaces(4)}
div.col-lg-6
h4.h6.text-center Tx Rate, 24hr
script var txRateDataDay = [];
each item, index in txStats.txCountStats.txRates
script txRateDataDay.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphRateDay", dataVar:"txRateDataDay", labels:txStats.txCountStats.txLabels, title:"Tx Rate, 24hr", xaxisTitle:"Block", xaxisStep:5, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug

View file

@ -6,19 +6,16 @@ html(lang="en")
meta(name="csrf-token", content=csrfToken)
meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no")
link(rel="stylesheet", href="./css/fonts.css", integrity="sha384-P92dMmtW9S5qrhDNNDr3kcdk00LJDKUy4Do4zFAO0+Br2b7iGHBWSVRgnnBsEpJS")
link(rel="stylesheet", href="./css/highlight.min.css", integrity="sha384-zhIsEafzyQWHSoMCQ4BfT8ZlRXQyIFwAHAJn32PNdsb8n6tVysGZSLpEEIvCskw4")
link(rel="stylesheet", href="./style/fonts.css", integrity="sha384-P92dMmtW9S5qrhDNNDr3kcdk00LJDKUy4Do4zFAO0+Br2b7iGHBWSVRgnnBsEpJS")
link(rel="stylesheet", href="./style/highlight.min.css", integrity="sha384-s4RLYRjGGbVqKOyMGGwfxUTMOO6D7r2eom7hWZQ6BjK2Df4ZyfzLXEkonSm0KLIQ")
if (session.uiTheme && session.uiTheme == "dark")
link(rel="stylesheet", href="./css/bootstrap-dark.min.css", integrity="sha384-yHrdw+uFPe64ykHEYlYL2lmT0s+veKi1+lmxNqB3jVMOl9iX0h9xDLxcgtFtiV6M")
link(rel="stylesheet", href="./css/dark-touchups.css")
if (userSettings.uiTheme == "light")
link(rel="stylesheet", href="./style/light.css", integrity="sha384-aUb+tfIBAXpVezvEdnu1PuRqntH8i/XMDZgw543c0vdV/1AjC8xV5JINblNI5GRA")
else
link(rel="stylesheet", href="./css/bootstrap.min.css", integrity="sha384-QO/VzYasHIIVEtFdqGXHU2wxSK0pjVxX4mrM68IQhYpi0vTCM0ZIuV/KQbPZMfXg")
link(rel="stylesheet", href="./style/dark.css", integrity="sha384-Ow3YhdsFrBZOGX+Z8jwJQKTq1ezm/PS8Jt5ND+g4j9PQkAmTPw3NaiBOIeFU5eLF")
link(rel='stylesheet', href='./css/styling.css')
link(rel="icon", type="image/png", href=`./img/logo/${config.coin.toLowerCase()}.png`)
@ -26,26 +23,28 @@ html(lang="en")
block headContent
title Explorer
body.bg-dark
nav.navbar.navbar-expand-lg.navbar-dark.bg-dark
div.container
include ./includes/shared-mixins.pug
body.bg-header-footer
nav.navbar.navbar-expand-lg.navbar-dark.bg-header-footer
.container
a.navbar-brand(href="./")
span
if (coinConfig.logoUrlsByNetwork && coinConfig.logoUrlsByNetwork[activeBlockchain])
img.header-image(src=coinConfig.logoUrlsByNetwork[activeBlockchain], alt="logo")
else
img.header-image(src="./img/logo/btc.svg", alt="logo")
img.header-image(src="./img/logo/logo.svg", alt="logo")
if (coinConfig.siteTitlesByNetwork && coinConfig.siteTitlesByNetwork[activeBlockchain])
span #{coinConfig.siteTitlesByNetwork[activeBlockchain]}
span.fw-light #{coinConfig.siteTitlesByNetwork[activeBlockchain]}
else
span Bitcoin Explorer
span.fw-light Bitcoin Explorer
button.navbar-toggler.navbar-toggler-end(type="button", data-bs-toggle="collapse", data-target="#navbarNav", aria-label="collapse navigation")
span.navbar-toggler-icon
div.collapse.navbar-collapse(id="navbarNav")
.collapse.navbar-collapse(id="navbarNav")
if (rpcClient)
ul.navbar-nav.me-auto
if (false)
@ -53,17 +52,45 @@ html(lang="en")
a.nav-link(href="./about")
span About
if (activeBlockchain != "main")
if (false && activeBlockchain != "main")
- var chainName = activeBlockchain == "test" ? "testnet" : activeBlockchain
li.nav-item
a.nav-link.text-warning(title=`Current node's chain: ${activeBlockchain}` data-bs-toggle="tooltip") [#{chainName}]
span.badge.bg-primary(style="margin-top: 0.75rem;", title=`Current node's chain: ${activeBlockchain}` data-bs-toggle="tooltip") #{chainName}
ul.navbar-nav
if (config.demoSite && homepage)
li.nav-item.dropdown.me-2
a.nav-link.dropdown-toggle.bg-header-footer-highlight.rounded(href="javascript:void(0)", role="button", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
span.fa-layers.fa-fw.me-1
i.fas.fa-circle.text-white(data-fa-transform="shrink-2")
i.fab.fa-bitcoin(style=`color: ${coinConfig.coinColorsByNetwork[activeBlockchain]}`)
div.dropdown-menu.dropdown-menu-end.shadow(aria-label="Tools Items")
span.dropdown-header Public Demo Sites
- var demoSites = ["main", "test", "signet"];
each demoSite in demoSites
a.dropdown-item(href=coinConfig.demoSiteUrlsByNetwork[demoSite])
span.fa-layers.fa-fw.ms-2
i.fas.fa-circle.fa-lg.me-2.text-white(data-fa-transform="shrink-2")
i.fab.fa-bitcoin.fa-lg.me-2(style=`color: ${coinConfig.coinColorsByNetwork[demoSite]}`)
| #{coinConfig.siteTitlesByNetwork[demoSite].replace("Explorer", "").replace("Bitcoin", "Mainnet")}
if (config.siteTools)
li.nav-item.dropdown
a.nav-link.dropdown-toggle(href="javascript:void(0)", role="button", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
span Tools
div.dropdown-menu.shadow(aria-label="Tools Items")
li.nav-item.dropdown.me-2
a.nav-link.rounded.bg-header-footer-highlight.dropdown-toggle(href="javascript:void(0)", role="button", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
i.fas.fa-tools.me-1
div.dropdown-menu.dropdown-menu-end.shadow(aria-label="Tools Items")
span.dropdown-header Tools
each itemIndex in config.site.prioritizedToolIdsList
- var item = config.siteTools[itemIndex];
@ -71,37 +98,8 @@ html(lang="en")
i(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
span #{item.name}
if (config.site.header.dropdowns)
each dropdown, ddIndex in config.site.header.dropdowns
li.nav-item.dropdown
a.nav-link.dropdown-toggle(href="javascript:void(0)", role="button", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
span #{dropdown.title}
div.dropdown-menu.shadow(aria-label=(dropdown.title + " Items"))
each dropdownLink in dropdown.links
a.dropdown-item(href=dropdownLink.url)
if (dropdownLink.imgUrl)
img(src=dropdownLink.imgUrl, style="width: 24px; height: 24px; margin-right: 8px;", alt=dropdownLink.name)
span #{dropdownLink.name}
form.form-inline.me-3(method="post", action="./search")
input(type="hidden", name="_csrf", value=csrfToken)
div.input-group.input-group-sm
input.form-control.form-control-sm(type="text", name="query", placeholder="block height/hash, txid, address", value=(query))
button.btn.btn-primary(type="submit")
i.fas.fa-search
ul.navbar-nav
li.nav-item.dropdown
a.nav-link.dropdown-toggle(href="javascript:void(0)", role="button", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
span Admin Tools
div.dropdown-menu.dropdown-menu-end.shadow(aria-label="Admin Tools Items")
a.dropdown-item(href="./admin/dashboard") Dashboard
a.dropdown-item(href="./admin/stats") Stats
li.nav-item.dropdown
a.nav-link.dropdown-toggle(href="javascript:void(0)", id="navbarDropdown", role="button", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
li.nav-item.dropdown.me-4
a.nav-link.rounded.bg-header-footer-highlight.dropdown-toggle(href="javascript:void(0)", id="navbarDropdown", role="button", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false")
i.fas.fa-cog.me-1
div.dropdown-menu.dropdown-menu-end.shadow(aria-labelledby="navbarDropdown")
if (coinConfig.currencyUnits)
@ -114,32 +112,20 @@ html(lang="en")
i.fas.fa-check
span #{item.name}
span.dropdown-header Theme
a.dropdown-item(href="./changeSetting?name=uiTheme&value=light")
if (session.uiTheme == "light" || session.uiTheme == "")
i.fas.fa-check
span Light
a.dropdown-item(href="./changeSetting?name=uiTheme&value=dark")
if (session.uiTheme == "dark")
i.fas.fa-check
span Dark
if (host && port && !homepage && config.site.header.showToolsSubheader)
div.container.mb-2.pt-2.d-lg-block.d-none.border-top(id="sub-menu", style="")
ul.nav
each itemIndex in config.site.subHeaderToolsList
- var item = config.siteTools[itemIndex];
li.nav-item
a.nav-link.text-white.px-2.text-decoration-underline.me-3(href=item.url)
i.me-1(class=item.fontawesome)
span #{item.name}
- var bodyBgColor = "#f8f9fa;";
if (session.uiTheme && session.uiTheme == "dark")
- bodyBgColor = "#0c0c0c;";
div.pb-4.pt-3.pt-md-4(style=("background-color: " + bodyBgColor))
div.container.px-2.px-sm-3
form.form-inline(method="post", action="./search", style="width: 325px;")
input(type="hidden", name="_csrf", value=csrfToken)
.input-group.input-group
input.form-control.form-control(type="text", name="query", placeholder="block height/hash, txid, address", value=(query))
button.btn.btn-primary(type="submit")
i.fas.fa-search
div.pb-4.pt-3.pt-md-4.bg-main(style=("background-color: " + bodyBgColor))
div.container.px-2.px-sm-3.mb-large
if (pageErrors && pageErrors.length > 0)
include includes/page-errors-modal.pug
@ -156,35 +142,29 @@ html(lang="en")
if (userMessage)
div.alert(class=(userMessageType ? `alert-${userMessageType}` : "alert-warning"), role="alert")
span #{userMessage}
div(style="min-height: 500px;")
block content
div(style="margin-bottom: 30px;")
if (config.demoSite && !crawlerBot)
include ./includes/donation-modal.pug
footer.footer.border-top.border-primary.bg-dark.pt-3.pb-1.px-3.text-white(style="border-width: 5px !important;")
footer.footer.border-top.border-primary.bg-header-footer.pt-3.pb-1.px-3.text-white(style="border-width: 5px !important;")
div.container
div.row
div.col-md-5
dl
dt Source
dd
a(href="https://github.com/janoside/btc-rpc-explorer") github.com/janoside/btc-rpc-explorer
a.footer-link(href="https://github.com/janoside/btc-rpc-explorer") github.com/janoside/btc-rpc-explorer
if (global.sourcecodeProjectMetadata)
div.mt-2
a.btn.btn-primary.btn-sm.me-3.mb-1.text-decoration-none(href="https://github.com/janoside/btc-rpc-explorer")
i.fas.fa-star.me-2
span.me-2 Star
i.fas.fa-star.fontawesome-shadow.text-warning.me-2
span.badge.bg-white.text-dark #{global.sourcecodeProjectMetadata.stargazers_count}
a.btn.btn-primary.btn-sm.me-3.mb-1.text-decoration-none(href="https://github.com/janoside/btc-rpc-explorer/fork")
i.fas.fa-code-branch.me-2
span.me-2 Fork
i.fas.fa-code-branch.fontawesome-shadow.me-2
span.badge.bg-white.text-dark #{global.sourcecodeProjectMetadata.forks_count}
@ -195,58 +175,98 @@ html(lang="en")
if (sourcecodeVersion)
br
span commit:
a(href=`https://github.com/janoside/btc-rpc-explorer/commit/${sourcecodeVersion}`) #{sourcecodeVersion}
a.footer-link(href=`https://github.com/janoside/btc-rpc-explorer/commit/${sourcecodeVersion}`) #{sourcecodeVersion}
br
span released: #{sourcecodeDate}
span(style="font-size: 0.9em;") (
a(href="./changelog") changelog
a.footer-link(href="./changelog") changelog
span )
if (config.demoSite)
dt Public Demos
dd
if (coinConfig.demoSiteUrl)
a(href=coinConfig.demoSiteUrl) #{coinConfig.demoSiteUrl}
if (coinConfig.demoSiteUrlsByNetwork && coinConfig.demoSiteUrlsByNetwork.main)
a.footer-link(href=coinConfig.demoSiteUrlsByNetwork.main) #{coinConfig.demoSiteUrlsByNetwork.main}
else
a(href="https://explorer.btc21.org") https://explorer.btc21.org
a.footer-link(href="https://explorer.btc21.org") https://explorer.btc21.org
div.mt-2
- var demoSites = [];
- demoSites.push({url:"https://explorer.btc21.org", logoUrl:"./img/logo/btc.svg", title:"Bitcoin Explorer"});
- demoSites.push({url:"https://testnet.btc21.org", logoUrl:"./img/logo/tbtc.svg", title:"Testnet Explorer"});
- demoSites.push({url:"https://signet.btc21.org", logoUrl:"./img/logo/signet.svg", title:"Signet Explorer"});
- var demoSites = ["main", "test", "signet"];
each demoSite in demoSites
a.me-2(href=demoSite.url, title=demoSite.title, data-bs-toggle="tooltip")
img(src=demoSite.logoUrl, alt=demoSite.title)
a.me-2(href="https://lnd-admin.btc21.org", title="LND Admin", data-bs-toggle="tooltip")
img(src=("./img/logo/lnd-admin.png"), style="width: 32px; height: 32px;", alt="LND Admin")
a.me-2(href=coinConfig.demoSiteUrlsByNetwork[demoSite], title=coinConfig.siteTitlesByNetwork[demoSite], data-bs-toggle="tooltip")
img(src=coinConfig.logoUrlsByNetwork[demoSite], alt=coinConfig.siteTitlesByNetwork[demoSite], style="width: 40px; height: 40px;")
div.col-md-7.text-md-end
dl
dd
button.btn.btn-primary(type="button", data-bs-toggle="modal", data-target="#exampleModalCenter")
i.fas.fa-heart.me-2
span Support Project
.btn-group.dropup.me-2
button.btn.dropdown-toggle.bg-header-footer-highlight.text-white(type="button" data-bs-toggle="dropdown" aria-expanded="false")
i.fas.fa-shield-alt.me-1
ul.dropdown-menu.dropdown-menu-end.shadow(aria-label="Admin Tools Items")
span.dropdown-header Admin Tools
a.dropdown-item(href="./admin/dashboard") Dashboard
a.dropdown-item(href="./admin/stats") Stats
- var darkTheme = (userSettings.uiTheme == "dark");
a.btn.btn-lg.fa-lg.bg-header-footer-highlight(href=`./changeSetting?name=uiTheme&value=${darkTheme ? "light" : "dark"}`, title=`Active Theme: <b>${darkTheme ? "Dark" : "Light"}</b><br/>Click to switch to <b>${darkTheme ? "Light" : "Dark"}</b>`, data-bs-toggle="tooltip", data-bs-html="true")
i.fas.fa-sun.text-warning.me-1
span.me-1
if (darkTheme)
i.fas.fa-toggle-on.text-primary
else
i.fas.fa-toggle-on.fa-rotate-180.text-warning
i.fas.fa-moon.text-primary
dd
dd
a.btn.btn-primary(href="https://donate.btc21.org", title="Support this project with a donation", data-bs-toggle="tooltip")
i.fas.fa-heart.fontawesome-shadow-dark.text-danger
a.btn.btn-primary.ms-2(href=`https://twitter.com/BitcoinExplorer`, title="Follow this project on Twitter", data-bs-toggle="tooltip")
i.fab.fa-twitter.fontawesome-shadow-dark.text-twitter
script(src="./js/jquery.min.js", integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh")
script(src="./js/popper.min.js", integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo")
script(src="./js/bootstrap.min.js", integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6")
script(defer, src="./js/fontawesome.min.js", integrity="sha384-eVEQC9zshBn0rFj4+TU78eNA19HMNigMviK/PU/FFjLXqa/GKPgX58rvt5Z8PLs7")
script(src="./js/jquery.min.js", integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK")
script(src="./js/bootstrap.bundle.min.js", integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf")
script(defer, src="./js/fontawesome.min.js", integrity="sha384-haqrlim99xjfMxRP6EWtafs0sB1WKcMdynwZleuUSwJR0mDeRYbhtY+KPMr+JL6f")
script(src="./js/highlight.min.js", integrity="sha384-xLrpH5gNLD6HMLgeDH1/p4FXigQ8T9mgNm+EKtCSXL0OJ5i1bnSi57dnwFuUMM9/")
script(src="./js/highlight.pack.js", integrity="sha384-OGoFdvlhYqw3L+BFpHxdz5136RO9tUlt7OZ2qQZ0N6Z9Qqx0rCQBsg9ko7X4vC64")
script(src="./js/site.js", integrity="sha384-VT6URWBBaQ+gBHYtXUO3QP6Rq8DnvRYIWx6nGW5l5u/usDvbitq+MkHc4ZxVTqzT")
script(src="./js/site.js", integrity="sha384-lRJsCA6PqJsSsdIfU9CtmBxuoCI2nZMQSphToDThRdHsK/unylcL0y41P/kRd49S")
script.
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover({html:true, container:"body"});
document.addEventListener("DOMContentLoaded", function(event) {
// enable tooltips everywhere
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// enable popovers everywhere
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
hljs.highlightAll();
});
hljs.initHighlightingOnLoad();
if (config.credentials.sentryUrl && config.credentials.sentryUrl.length > 0)
script(src="./js/sentry.min.js", integrity="sha384-da/Bo2Ah6Uw3mlhl6VINMblg2SyGbSnULKrukse3P5D9PTJi4np9HoKvR19D7zOL", crossorigin="anonymous")

View file

@ -4,88 +4,86 @@ block headContent
title Mempool Summary
block content
h1.h3 Mempool Summary
hr
+pageTitle("Mempool Summary")
+dismissableInfoAlert("mempoolSummaryNoteDismissed", "About Mempool Summary...")
.mb-2 This tool summarizes data about the transactions in your node's <b>mempool</b> - the set of transactions that your node has received from the network but are not yet confirmed in a block.
.mb-2 The fee-rate summary data can be helpful for deciding what fee is necessary to get a transaction included in a future block, depending on its priority.
div#progress-wrapper.mb-huge
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading mempool transactions:
span Loading mempool transactions:
span(id="progress-text")
div.progress.mt-2(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
div(id="main-content", style="display: none;")
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6.mb-0 Summary
hr
+contentSection("Summary")
+summaryRow(5)
+summaryItem("Transactions")
span#tx-count
div.clearfix
div.row
div.summary-table-label Tx Count
div.summary-table-content.font-monospace(id="tx-count")
+summaryItem("Memory Usage")
span#mem-usage
div.row
div.summary-table-label Memory Usage
div.summary-table-content.font-monospace(id="mem-usage")
+summaryItem("Total Fees")
span#total-fees
div.row
div.summary-table-label Total Fees
div.summary-table-content.font-monospace(id="total-fees")
+summaryItem("Avg Fee")
span#avg-fee
div.row
div.summary-table-label Avg Fee
div.summary-table-content.font-monospace(id="avg-fee")
div.row
div.summary-table-label Avg Fee Rate
div.summary-table-content.font-monospace(id="avg-fee-rate")
+summaryItem("Avg Fee Rate", null, "sat/vB")
span#avg-fee-rate
div#detail-charts-and-data
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6.mb-0 Transactions by fee rate
hr
+contentSection("Fee Rate Distribution")
canvas.mb-3(id="mempoolBarChart", height="100")
canvas.mb-3(id="mempoolBarChart", height="100")
hr.mt-3
div.table-responsive
table.table.table-striped.mb-3
thead
tr
th Fee Rate
th.text-end Tx Count
th.text-end Total Fees
th.text-end Avg Fee
th.text-end Avg Fee Rate
tbody(id="fee-rate-table-body")
tr(id="fee-rate-table-row-prototype", style="display: none;")
td.font-monospace.data-label
td.font-monospace.text-end.data-count
td.font-monospace.text-end.data-total-fees
td.font-monospace.text-end.data-avg-fee
td.font-monospace.text-end.data-fee-rate
.table-responsive
table.table.table-striped.table-borderless.mb-3
thead
tr
th Fee Rate
th.text-end Tx Count
th.text-end Total Fees
th.text-end Avg Fee
th.text-end Avg Fee Rate
small.fw-normal.text-muted.ms-2 (sat/vB)
tbody(id="fee-rate-table-body")
tr(id="fee-rate-table-row-prototype", style="display: none;")
td.font-monospace.data-label
td.font-monospace.text-end.data-count
td.font-monospace.text-end.data-total-fees
td.font-monospace.text-end.data-avg-fee
td.font-monospace.text-end.data-fee-rate
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6.mb-0 Transactions by size
hr
tr(id="empty-row-to-fix-striped-coloring", style="display: none;")
canvas.mb-3(id="txSizesBarChart", height="100")
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6.mb-0 Transactions by age
hr
+contentSection("Size Distribution")
canvas.mb-3(id="txSizesBarChart", height="100")
canvas.mb-3(id="txAgesBarChart", height="100")
+contentSection("Age Distribution")
canvas.mb-3(id="txAgesBarChart", height="100")
block endOfBody
script(src="./js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
+graphPageScriptSetup
script(src='./js/decimal.js')
script.
var txidChunks = !{JSON.stringify(mempooltxidChunks)};
var satoshiPerByteBucketMaxima = !{JSON.stringify(satoshiPerByteBucketMaxima)};
@ -155,7 +153,7 @@ block endOfBody
$("#avg-fee-rate").text(summary.averageFeePerByte);
$.ajax({
url: `./api/utils/formatLargeNumber/${summary.totalBytes},2`
url: `./api/utils/formatLargeNumber/${summary.totalBytes},1`
}).done(function(result) {
$("#mem-usage").html(`<span>${result[0]} <small>${result[1].abbreviation}B</small></span>`);
@ -164,7 +162,7 @@ block endOfBody
updateCurrencyValue($("#total-fees"), summary.totalFees);
updateCurrencyValue($("#avg-fee"), summary.averageFee);
updateFeeRateValue($("#avg-fee-rate"), summary.averageFeePerByte, 2);
updateFeeRateValue($("#avg-fee-rate"), summary.averageFeePerByte, 2, false);
//$("#summary-json").text(JSON.stringify(summary, null, 4));
@ -186,9 +184,17 @@ block endOfBody
display: false
},
scales: {
xAxes: [{
gridLines: {
color: gridLineColor
}
}],
yAxes: [{
ticks: {
beginAtZero:true
},
gridLines: {
color: gridLineColor
}
}]
}
@ -211,9 +217,17 @@ block endOfBody
display: false
},
scales: {
xAxes: [{
gridLines: {
color: gridLineColor
}
}],
yAxes: [{
ticks: {
beginAtZero:true
},
gridLines: {
color: gridLineColor
}
}]
}
@ -236,9 +250,17 @@ block endOfBody
display: false
},
scales: {
xAxes: [{
gridLines: {
color: gridLineColor
}
}],
yAxes: [{
ticks: {
beginAtZero:true
},
gridLines: {
color: gridLineColor
}
}]
}
@ -264,7 +286,7 @@ block endOfBody
updateCurrencyValue(row.find(".data-total-fees"), item.totalFees);
updateCurrencyValue(row.find(".data-avg-fee"), item.totalFees / item.count);
updateFeeRateValue(row.find(".data-fee-rate"), item.totalFees / item.totalBytes, 2);
updateFeeRateValue(row.find(".data-fee-rate"), item.totalFees / item.totalBytes, 2, false);
}
row.show();

19
views/mempool-tx.pug Normal file
View file

@ -0,0 +1,19 @@
extends layout
block headContent
title Mempool Transactions
block content
+pageTitle("Mempool Transactions")
if (false)
pre
code #{JSON.stringify(transactions, null, 4)}
if (txCount > 0)
+contentSection(`${txCount.toLocaleString()} Transaction${txCount == 1 ? "" : "s"}`, false, null, false, false)
+txList(transactions, txCount, limit, offset, txInputsByTransaction, {mempoolDetailsByTxid:mempoolDetailsByTxid})
else
p No unconfirmed transactions found

View file

@ -4,65 +4,31 @@ block headContent
title Mining Summary
block content
h1.h3 Mining Summary
hr
+pageTitle("Mining Summary")
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Block Range
hr
div.clearfix
div.float-start.me-4
div(id="time-range-buttons")
h3.h6 Latest N Blocks
div.btn-group
a.btn.btn-primary.block-count-btn(href="javascript:void(0)", data-blockCount="144") 144
small.ms-2 (~1d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="432") 432
small.ms-2 (~3d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="1008") 1,008
small.ms-2 (~7d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="4320") 4,320
small.ms-2 (~30d)
+dismissableInfoAlert("miningSummaryPageNoteDismissed", "About Mining Summary...")
| !{markdown("The **Mining Summary** tool queries a range of blocks from Bitcoin Core and tries to identify the miner for each block. The identification of a mining entity is an imperfect process, relying on optional flagging by anonymous entities (though, in practice, it tends to work well).")}
| Use the <b>Filters</b> section below to select the range of blocks to analyze.
div.float-start.me-4
div(id="block-selections-buttons")
h3.h6 Preconfigured
div.dropdown
button.btn.btn-outline-primary.dropdown-toggle(type="button", id="preconfigured-dropdown", data-bs-toggle="dropdown", aria-haspopup="true", aria-expanded="false") Selections
div.dropdown-menu(aria-labelledby="preconfigured-dropdown")
a.dropdown-item(href="javascript:void(0)", data-blocks="0-199") First 200 Blocks
a.dropdown-item(href="javascript:void(0)", data-blocks="209900-210100") First Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="419900-420100") Second Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="629900-630100") Third Halving ±100
a.dropdown-item(href="javascript:void(0)", data-blocks="481724-481924") SegWit Activation ±100
+contentSection("Filters")
+blockRangeFilters
div.float-start
div(id="time-range-buttons")
h3.h6 Custom Range
form.form-inline.me-3(id="custom-range-form")
div.input-group
input.form-control(type="text", id="custom-range-start", placeholder="min height", style="width: 125px;")
input.form-control(type="text", id="custom-range-end", placeholder="max height", style="width: 125px;")
button.btn.btn-primary(type="submit") Go
div#progress-wrapper.mb-huge
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading blocks:
span Loading blocks:
span(id="block-progress-text")
div.progress.mt-2(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
div.card.shadow-sm.mb-3(id="summary-table", style="display: none;")
div.card-body
div.table-responsive
table.table.table-striped.mb-0
div#summary-table(style="display: none;")
+contentSection("Miner Breakdown")
.table-responsive
table.table.table-borderless.table-striped.mb-0
thead
tr
th.data-header Miner
@ -73,7 +39,8 @@ block content
tbody(id="summary-table-body")
tr(id="miner-summary-prototype", style="display: none;")
td.data-cell.font-monospace.miner-name
td.data-cell.font-monospace
span.badge.bg-primary.miner-name
td.data-cell.font-monospace.text-end
span.miner-block-count
small.text-muted.miner-block-percent.ms-2
@ -91,6 +58,8 @@ block content
//- var currencyValue = new Decimal(summary.totalFeesCollected);
//include ./includes/value-display.pug
tr(id="hidden-row-to-fix-stripe-pattern", style="display: none;")
tr(id="miner-summary-totals", style="display: none;")
th.data-cell.font-monospace.miner-name Total
th.data-cell.font-monospace.text-end

View file

@ -4,8 +4,7 @@ block headContent
title Node Status
block content
h1.h3 Node Status
hr
+pageTitle("Node Status")
if (getblockchaininfo)
if (false)
@ -18,214 +17,191 @@ block content
if (false)
pre
code.json.bg-light #{JSON.stringify(getblockchaininfo, null, 4)}
code.json #{JSON.stringify(getblockchaininfo, null, 4)}
ul.nav.nav-tabs.mb-3
li.nav-item
a.nav-link.active(data-bs-toggle="tab", href="#tab-details", role="tab") Details
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json", role="tab") JSON
+pageTabs(["Details", "JSON"])
div.tab-content
div.tab-pane.active(id="tab-details", role="tabpanel")
div.card.rounded-lg.shadow-sm.mb-3
div.card-body.px-2.px-sm-3
h3.h6.mb-0 Node Summary
hr
+pageTab("Details", true)
div.clearfix
div.row
div.summary-table-label Version
div.summary-table-content.font-monospace #{getnetworkinfo.version} (#{getnetworkinfo.subversion})
if (getblockchaininfo.warnings && getblockchaininfo.warnings.trim().length > 0)
+contentSection("Active Warnings")
if (getblockchaininfo.warnings && getblockchaininfo.warnings.trim().length > 0)
span.text-danger #{getblockchaininfo.warnings}
else
span.text-success None
div.row
div.summary-table-label Protocol Version
div.summary-table-content.font-monospace #{getnetworkinfo.protocolversion}
+contentSection("Node Summary")
+summaryRow(4)
+summaryItem("Version")
| #{getnetworkinfo.version} (#{getnetworkinfo.subversion})
div.row
div.summary-table-label Status
div.summary-table-content.font-monospace
if (getblockchaininfo.initialblockdownload || getblockchaininfo.headers > getblockchaininfo.blocks)
span Initial block download progress #{(100 * getblockchaininfo.verificationprogress).toLocaleString()}%
else
span.text-success Synchronized with network
+summaryItem("Protocol Version")
| #{getnetworkinfo.protocolversion}
div.row
div.summary-table-label Uptime
div.summary-table-content.font-monospace
- var startTimeAgo = moment.duration(uptimeSeconds * 1000);
span #{startTimeAgo.format()}
+summaryItem("Status")
if (getblockchaininfo.initialblockdownload || getblockchaininfo.headers > getblockchaininfo.blocks)
span Initial block download progress #{(100 * getblockchaininfo.verificationprogress).toLocaleString()}%
else
span.text-success Synchronized
div.row
div.summary-table-label Warnings
div.summary-table-content.font-monospace
if (getblockchaininfo.warnings && getblockchaininfo.warnings.trim().length > 0)
span.text-danger #{getblockchaininfo.warnings}
else
span.text-success None
+summaryItem("Uptime")
- var startTimeAgo = moment.duration(uptimeSeconds * 1000);
span #{startTimeAgo.format()}
div.card.shadow-sm.mb-3
div.card-body.px-2.px-sm-3
h3.h6.mb-0 Blockchain Summary
hr
+contentSection("Blockchain")
+summaryRow(4 + (getblockchaininfo.size_on_disk ? 1 : 0) + (getblockchaininfo.pruned ? 1 : 0))
+summaryItem("Chain")
if (getblockchaininfo.chain == "main")
span.text-success #{getblockchaininfo.chain}
else
span.text-warning #{getblockchaininfo.chain}
div.clearfix
div.row
div.summary-table-label Chain
div.summary-table-content.font-monospace #{getblockchaininfo.chain}
+summaryItem("Block Count")
| #{getblockchaininfo.blocks.toLocaleString()}
if (getblockchaininfo.headers != getblockchaininfo.blocks)
br
small.text-muted (headers: #{getblockchaininfo.headers.toLocaleString()})
div.row
div.summary-table-label Block Count
div.summary-table-content.font-monospace #{getblockchaininfo.blocks.toLocaleString()}
+summaryItem("Difficulty")
- var difficultyData = utils.formatLargeNumber(getblockchaininfo.difficulty, 3);
span.border-dotted(title=parseFloat(getblockchaininfo.difficulty).toLocaleString(), data-bs-toggle="tooltip")
span #{difficultyData[0]}
span x 10
sup #{difficultyData[1].exponent}
if (getblockchaininfo.size_on_disk)
+summaryItem("Data Size")
- var sizeData = utils.formatLargeNumber(getblockchaininfo.size_on_disk, 1);
| #{sizeData[0]} #{sizeData[1].abbreviation}B
if (getblockchaininfo.pruned)
+summaryItem("Pruned?", "Whether your node prunes the blockchain. With pruning active, your node will delete some data that is non-essential for future verification needs, in order to reduce disk usage.")
span.text-warning Yes
small.ms-2.text-muted
| (
span.border-dotted(title="This is the latest block for which your node has all block data; blocks older than this one have had some or all non-essential details deleted.", data-bs-toggle="tooltip")
| #{getblockchaininfo.pruneheight.toLocaleString()}+
| )
if (false)
br
span.text-muted (headers: #{getblockchaininfo.headers.toLocaleString()})
div.row
div.summary-table-label Difficulty
div.summary-table-content.font-monospace
- var difficultyData = utils.formatLargeNumber(getblockchaininfo.difficulty, 3);
span.border-dotted(title=parseFloat(getblockchaininfo.difficulty).toLocaleString(), data-bs-toggle="tooltip")
span #{difficultyData[0]}
span x 10
sup #{difficultyData[1].exponent}
span.border-dotted(title="This is the approximate amount of non-essential blockchain data that your node will store.", data-bs-toggle="tooltip") Prune target size
if (getblockchaininfo.softforks)
- var forkUrls = {"bip34":"https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki", "bip65":"https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki", "bip66":"https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki", "csv":"https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki", "segwit":"https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki", "taproot":"https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki"};
div.row
div.summary-table-label Soft Forks
div.summary-table-content.font-monospace
ul.list-unstyled
each item, itemName in getblockchaininfo.softforks
li
div.d-inline-block.text-end(style="width: 75px;")
if (forkUrls[itemName])
a.fw-bold(href=forkUrls[itemName]) #{itemName}
| :&nbsp;
else
span.fw-bold #{itemName}
| :&nbsp;
if (item.type == "bip9")
span.me-2.ms-2 #{item.type} - #{JSON.stringify(item.bip9)}
span :&nbsp;
- var pruneTargetSize = utils.formatLargeNumber(getblockchaininfo.prune_target_size, 1);
span #{pruneTargetSize[0]} #{pruneTargetSize[1].abbreviation}B
if (item.active)
span.me-2
span.text-success active
small.text-muted.ms-2 (since: #
a(href=`./block-height/${item.height}`) #{item.height.toLocaleString()}
| )
+summaryItem("Data Indexes")
ul.list-unstyled.mb-0
li
span.me-2 txindex
if (getindexinfo.txindex)
i.fas.fa-check.text-success
else
span.border-dotted(title="Your node's lack of a 'transaction index' means this explorer will be unable to show all blockchain data. See the project README for more details.", data-bs-toggle="tooltip")
i.fas.fa-times.text-danger
if (getblockchaininfo.size_on_disk)
- var sizeData = utils.formatLargeNumber(getblockchaininfo.size_on_disk, 2);
li
span.me-2 blockfilterindex
if (getindexinfo["basic block filter index"])
i.fas.fa-check.text-success
else
i.fas.fa-times.text-danger
div.row
div.summary-table-label Blockchain Size
div.summary-table-content.font-monospace #{sizeData[0]} #{sizeData[1].abbreviation}B
if (getblockchaininfo.softforks)
- var forkUrls = {"bip34":"https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki", "bip65":"https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki", "bip66":"https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki", "csv":"https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki", "segwit":"https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki", "taproot":"https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki"};
if (getblockchaininfo.pruned)
div.row
div.summary-table-label
span.border-dotted(title="Whether your node prunes the blockchain. With pruning active, your node will delete some data that is non-essential for future verification needs, in order to reduce disk usage.", data-bs-toggle="tooltip") Pruning?
div.summary-table-content.font-monospace
span.fw-bold Yes
br
span.border-dotted(title="This is the latest block for which your node has all block data; blocks older than this one have had some or all non-essential details deleted via pruning.", data-bs-toggle="tooltip") Prune height
+contentSection("Soft Forks")
+summaryRow(utils.objectProperties(getblockchaininfo.softforks).length)
each item, itemName in getblockchaininfo.softforks
+summaryItem(itemName)
if (item.active)
span.me-2
span.text-success active
small.text-muted.ms-2 (
a.border-dotted(href=`./block-height/${item.height}`, title=`${itemName} has been active since block ${item.height}`, data-bs-toggle="tooltip") #{item.height.toLocaleString()}
| +)
span : #{getblockchaininfo.pruneheight.toLocaleString()}
else if (item.type == "bip9")
span.me-2.ms-2 #{item.type} - #{JSON.stringify(item.bip9)}
br
span.border-dotted(title="This is the approximate amount of non-essential blockchain data that your node will store.", data-bs-toggle="tooltip") Prune target size
span :&nbsp;
- var pruneTargetSize = utils.formatLargeNumber(getblockchaininfo.prune_target_size, 2);
span #{pruneTargetSize[0]} #{pruneTargetSize[1].abbreviation}B
div.row
div.summary-table-label Indexes
div.summary-table-content.font-monospace
ul.list-unstyled
li
span.fw-bold txindex
| :&nbsp;
if (getindexinfo.txindex)
span.text-success available
else
span.text-danger.border-dotted(title="Your node's lack of a 'transaction index' means this explorer will be unable to show all blockchain data. See the project README for more details.", data-bs-toggle="tooltip") unavailable
li
span.fw-bold blockfilterindex
| :&nbsp;
if (getindexinfo["basic block filter index"])
span.text-success available
else
span.text-danger unavailable
if (forkUrls[itemName])
small
a(href=forkUrls[itemName], title=`More info about ${itemName}`, data-bs-toggle="tooltip", target="_blank")
i.fas.fa-external-link-alt.small
div.card.shadow-sm.mb-3
div.card-body.px-2.px-sm-3
h3.h6.mb-0 Network Summary
+contentSection("Network")
+summaryRow(3 + (getnetworkinfo.networks ? 1 : 0))
+summaryItem("Host")
| #{global.rpcClient.host}
span.text-muted :#{global.rpcClient.port}
+summaryItem("Peers")
| #{getnetworkinfo.connections.toLocaleString()}
if (getnetworkinfo.connections_in)
br
span.text-muted (#{getnetworkinfo.connections_in.toLocaleString()} in; #{getnetworkinfo.connections_out.toLocaleString()} out)
+summaryItem("Traffic")
- var downData = utils.formatLargeNumber(getnettotals.totalbytesrecv, 1);
- var downRateData = utils.formatLargeNumber(getnettotals.totalbytesrecv / uptimeSeconds, 1);
- var upData = utils.formatLargeNumber(getnettotals.totalbytessent, 1);
- var upRateData = utils.formatLargeNumber(getnettotals.totalbytessent / uptimeSeconds, 1);
i.fas.fa-arrow-down.me-2
span.me-2 #{downData[0]} #{downData[1].abbreviation}B
small.text-muted (#{downRateData[0]} #{downRateData[1].abbreviation}B/s)
br
i.fas.fa-arrow-up.me-2
span.me-2 #{upData[0]} #{upData[1].abbreviation}B
small.text-muted (#{upRateData[0]} #{upRateData[1].abbreviation}B/s)
if (getnetworkinfo.networks)
+summaryItem("Interfaces")
each item, index in getnetworkinfo.networks
div
div.d-inline-block.text-end(style="width: 75px;")
span.me-2 #{item.name}
if (item.proxy)
small.text-muted.me-2 (#{item.proxy})
if (item.reachable)
span.border-dotted(title="Reachable", data-bs-toggle="tooltip")
i.fas.fa-check.text-success
else
span.border-dotted(title="Unreachable", data-bs-toggle="tooltip")
i.fas.fa-times.text-danger
if (getnetworkinfo.localaddresses)
hr
div.clearfix
div.row
div.summary-table-label Host : Port
div.summary-table-content.font-monospace #{global.rpcClient.host + " : " + global.rpcClient.port}
.ms-3
+summaryTitle("Addresses")
.lead
ul
each item, index in getnetworkinfo.localaddresses
li
span.me-2 #{item.address}:#{item.port}
small.text-muted (score: #{item.score.toLocaleString()})
div.row
div.summary-table-label Peers
div.summary-table-content.font-monospace
span #{getnetworkinfo.connections.toLocaleString()}
if (getnetworkinfo.connections_in)
br
span.text-muted (#{getnetworkinfo.connections_in.toLocaleString()} in; #{getnetworkinfo.connections_out.toLocaleString()} out)
div.row
div.summary-table-label Network Traffic
div.summary-table-content.font-monospace
- var downData = utils.formatLargeNumber(getnettotals.totalbytesrecv, 2);
- var downRateData = utils.formatLargeNumber(getnettotals.totalbytesrecv / uptimeSeconds, 2);
- var upData = utils.formatLargeNumber(getnettotals.totalbytessent, 2);
- var upRateData = utils.formatLargeNumber(getnettotals.totalbytessent / uptimeSeconds, 2);
span Total Download: #{downData[0]} #{downData[1].abbreviation}B
span.text-muted (avg #{downRateData[0]} #{downRateData[1].abbreviation}B/s)
br
span Total Upload: #{upData[0]} #{upData[1].abbreviation}B
span.text-muted (avg #{upRateData[0]} #{upRateData[1].abbreviation}B/s)
if (getnetworkinfo.networks)
div.row
div.summary-table-label Interfaces
div.summary-table-content.font-monospace
each item, index in getnetworkinfo.networks
div
div.d-inline-block.text-end(style="width: 75px;")
span.fw-bold #{item.name}
| :&nbsp;
if (item.reachable)
span.text-success reachable
else
span.text-danger unreachable
if (item.proxy)
span.text-muted.ms-3 (proxy: #{item.proxy})
if (getnetworkinfo.localaddresses)
div.row
div.summary-table-label Local Addresses
div.summary-table-content.font-monospace
each item, index in getnetworkinfo.localaddresses
div #{item.address}:#{item.port}
span.text-muted.ms-3 (score: #{item.score.toLocaleString()})
if (getnetworkinfo.localaddresses.length == 0)
li None
div.tab-pane(id="tab-json", role="tabpanel")
+pageTab("JSON")
ul.nav.nav-pills.mb-3
li.nav-item
@ -239,43 +215,26 @@ block content
div.tab-content
div.tab-pane.active(id="tab-getblockchaininfo", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 getblockchaininfo
hr
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(getblockchaininfo, null, 4)}
+contentSection("getblockchaininfo")
pre
code.json(data-lang="json") #{JSON.stringify(getblockchaininfo, null, 4)}
div.tab-pane(id="tab-getnettotals", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 getnettotals
hr
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(getnettotals, null, 4)}
+contentSection("getnettotals")
pre
code.json(data-lang="json") #{JSON.stringify(getnettotals, null, 4)}
div.tab-pane(id="tab-getnetworkinfo", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 getnetworkinfo
hr
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(getnetworkinfo, null, 4)}
+contentSection("getnetworkinfo")
pre
code.json(data-lang="json") #{JSON.stringify(getnetworkinfo, null, 4)}
div.tab-pane(id="tab-getindexinfo", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 getindexinfo
hr
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(global.getindexinfo, null, 4)}
+contentSection("getindexinfo")
pre
code.json(data-lang="json") #{JSON.stringify(global.getindexinfo, null, 4)}

View file

@ -3,7 +3,7 @@ extends layout
block headContent
title Peers
link(rel="stylesheet", href="./css/dataTables.bootstrap4.min.css", integrity="sha384-EkHEUZ6lErauT712zSr0DZ2uuCmi3DoQj6ecNdHQXpMpFNGAQ48WjfXCE5n20W+R")
link(rel="stylesheet", href="./style/dataTables.bootstrap4.min.css", integrity="sha384-EkHEUZ6lErauT712zSr0DZ2uuCmi3DoQj6ecNdHQXpMpFNGAQ48WjfXCE5n20W+R")
link(rel="stylesheet", href="./leaflet/leaflet.css", integrity="sha384-6wKUKNzA6h/S6gZ1lWQppeGaVXvK1AUAsEznGBghzlEu1fNcxJGYVRiroSHr+OwU")
@ -17,168 +17,134 @@ block headContent
#map { height: 700px; }
block content
h1.h3 #{peerSummary.getpeerinfo.length}
if (peerSummary.getpeerinfo.length == 1)
span Peer
else
span Peers
hr
+pageTitle(`${peerSummary.getpeerinfo.length} Peer${peerSummary.getpeerinfo.length == 1 ? "" : "s"}`)
+pageTabs(["Details", "JSON"])
ul.nav.nav-tabs.mb-3
li.nav-item
a.nav-link.active(data-bs-toggle="tab", href="#tab-summary", role="tab") Summary
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json", role="tab") JSON
div.tab-content
div.tab-pane.active(id="tab-summary", role="tabpanel")
.tab-content
+pageTab("Details", true)
if (peerIpSummary && peerIpSummary.ips)
div.mb-3(id="map")
div.row
div.col-md-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Top Versions
hr
+contentSection("Top Versions")
table.table.table-borderless.table-striped.table-responsive-sm
thead
tr
th
th.data-header Version
th.data-header Count
tbody
each item, index in peerSummary.versionSummary
tr(class=(index >= 5 ? "versions-hidden-rows" : false))
td.data-cell.fw-bold #{index + 1}
table.table.table-striped.table-responsive-sm
thead
tr
th
th.data-header Version
th.data-header Count
tbody
each item, index in peerSummary.versionSummary
tr(class=(index >= 5 ? "versions-hidden-rows" : false))
td.data-cell.fw-bold #{index + 1}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
div.col-md-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Top Service Flags
hr
+contentSection("Top Service Flags")
table.table.table-borderless.table-striped.table-responsive-sm
thead
tr
th
th.data-header Services
th.data-header Count
tbody
each item, index in peerSummary.servicesSummary
tr(class=(index >= 5 ? "services-hidden-rows" : false))
td.data-cell.fw-bold #{index + 1}
table.table.table-striped.table-responsive-sm
thead
tr
th
th.data-header Services
th.data-header Count
tbody
each item, index in peerSummary.servicesSummary
tr(class=(index >= 5 ? "services-hidden-rows" : false))
td.data-cell.fw-bold #{index + 1}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
div.row
if (peerSummary.networkTypeSummary)
div.col-md-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Network Types
hr
table.table.table-striped.table-responsive-sm
thead
+contentSection("Network Types")
table.table.table-borderless.table-striped.table-responsive-sm
thead
tr
th
th.data-header Network Type
th.data-header Count
tbody
each item, index in peerSummary.networkTypeSummary
tr
th
th.data-header Network Type
th.data-header Count
tbody
each item, index in peerSummary.networkTypeSummary
tr
td.data-cell.fw-bold #{index + 1}
td.data-cell.fw-bold #{index + 1}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
if (peerSummary.connectionTypeSummary)
div.col-md-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Connection Types
hr
table.table.table-striped.table-responsive-sm
thead
+contentSection("Connection Types")
table.table.table-borderless.table-striped.table-responsive-sm
thead
tr
th
th.data-header Connection Type
th.data-header Count
tbody
each item, index in peerSummary.connectionTypeSummary
tr
th
th.data-header Connection Type
th.data-header Count
tbody
each item, index in peerSummary.connectionTypeSummary
tr
td.data-cell.fw-bold #{index + 1}
td.data-cell.fw-bold #{index + 1}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
td.data-cell.font-monospace #{item[0]}
td.data-cell.font-monospace #{item[1].toLocaleString()}
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 #{peerSummary.getpeerinfo.length}
if (peerSummary.getpeerinfo.length == 1)
span Peer
else
span Peers
+contentSection(`${peerSummary.getpeerinfo.length.toLocaleString()} Peer${peerSummary.getpeerinfo.length == 1 ? "" : "s"}`)
table.table.table-borderless.table-striped.table-responsive-sm.data-table.mt-4
thead
tr
th
th.data-header Version
th.data-header Address
th.data-header Services
hr
if (peerIpSummary && peerIpSummary.ips)
th.data-header Location
th.data-header Last Send / Receive
tbody
each item, index in peerSummary.getpeerinfo
- var lastSendAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(item.lastsend) * 1000)))).format().replace("milliseconds", "ms");
- var lastRecvAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(item.lastrecv) * 1000)))).format().replace("milliseconds", "ms");
table.table.table-striped.table-responsive-sm.data-table.mt-4
thead
tr
th
th.data-header Version
th.data-header Address
th.data-header Services
th.data-cell #{index + 1}
if (peerIpSummary && peerIpSummary.ips)
th.data-header Location
td.data-cell.font-monospace #{item.subver}
td.data-cell.font-monospace #{item.addr}
td.data-cell.font-monospace #{item.services}
th.data-header Last Send / Receive
if (peerIpSummary.ips)
td.data-cell.font-monospace
- var ipAddr = item.addr.substring(0, item.addr.lastIndexOf(":"));
if (peerIpSummary.ips && peerIpSummary.ips.includes(ipAddr))
- var ipDetails = peerIpSummary.detailsByIp[ipAddr];
if (ipDetails)
if (ipDetails.city)
span #{ipDetails.city},
if (ipDetails.region_name)
span #{ipDetails.region_name},
if (ipDetails.country_name)
span #{ipDetails.country_name}
else
span ?
tbody
each item, index in peerSummary.getpeerinfo
- var lastSendAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(item.lastsend) * 1000)))).format().replace("milliseconds", "ms");
- var lastRecvAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(item.lastrecv) * 1000)))).format().replace("milliseconds", "ms");
- var ipAddr = null;
tr
th.data-cell #{index + 1}
td.data-cell.font-monospace #{item.subver}
td.data-cell.font-monospace #{item.addr}
td.data-cell.font-monospace #{item.services}
if (peerIpSummary.ips)
td.data-cell.font-monospace
- var ipAddr = item.addr.substring(0, item.addr.lastIndexOf(":"));
if (peerIpSummary.ips && peerIpSummary.ips.includes(ipAddr))
- var ipDetails = peerIpSummary.detailsByIp[ipAddr];
if (ipDetails)
if (ipDetails.city)
span #{ipDetails.city},
if (ipDetails.region_name)
span #{ipDetails.region_name},
if (ipDetails.country_name)
span #{ipDetails.country_name}
else
span ?
- var ipAddr = null;
td.data-cell.font-monospace #{lastSendAgo} / #{lastRecvAgo}
td.data-cell.font-monospace #{lastSendAgo} / #{lastRecvAgo}
div.tab-pane(id="tab-json", role="tabpanel")
+pageTab("JSON")
each item, index in peerSummary.getpeerinfo
div.border-bottom.p-1
a(href="javascript:void(0)" onclick=("javascript:var peer = document.getElementById('peerinfo_" + index + "'); peer.style.display = peer.style.display === 'none' ? '' : 'none';"))
@ -186,25 +152,17 @@ block content
span.font-monospace #{item.addr}
div.p-3(style="display: none;", id=`peerinfo_${index}`)
div.card.shadow-sm.mb-3
div.card-body
h6.mb-0 Peer Details
hr
div.highlight
pre
code.json.bg-light #{JSON.stringify(item, null, 4)}
.p-3(style="display: none;", id=`peerinfo_${index}`)
+contentSection("Peer Details")
pre
code.json #{JSON.stringify(item, null, 4)}
if (peerIpSummary && peerIpSummary.detailsByIp && peerIpSummary.detailsByIp[item.addr.substring(0, item.addr.lastIndexOf(":"))])
div.card.shadow-sm.mb-3
div.card-body
h6.mb-0 IP Geo-Location Info
hr
div.highlight
pre
code.json.bg-light #{JSON.stringify(peerIpSummary.detailsByIp[item.addr.substring(0, item.addr.lastIndexOf(":"))], null, 4)}
+contentSection("Location Info")
pre
code.json #{JSON.stringify(peerIpSummary.detailsByIp[item.addr.substring(0, item.addr.lastIndexOf(":"))], null, 4)}
block endOfBody

View file

@ -10,13 +10,14 @@ block headContent
}
block content
h1.h3.mb-0 RPC Browser
hr
+pageTitle("RPC Browser")
if (gethelp)
div.row
div.col-md-9
if (methodhelp)
+contentSection(`Command: ${method}`)
div.card.shadow-sm.mb-3
div.card-body
div.clearfix
@ -48,7 +49,7 @@ block content
h3.h6.mt-3 Result
pre.border.mb-3
code.json.bg-light(data-lang="json") #{JSON.stringify(methodResult, null, 4)}
code.json(data-lang="json") #{JSON.stringify(methodResult, null, 4)}
hr
@ -106,31 +107,29 @@ block content
input.btn.btn-primary.px-3(type="submit", name="execute", value="Execute")
div.tab-pane(id="tab-help-content", role="tabpanel")
pre.border
code.json.bg-light(data-lang="txt") #{methodhelp.string}
pre
code.json(data-lang="txt") #{methodhelp.string}
if (methodhelp.args && methodhelp.args.length > 0)
div.tab-pane(id="tab-parsed-args", role="tabpanel")
pre.border
code.json.bg-light(data-lang="json") #{JSON.stringify(methodhelp.args, null, 4)}
code.json(data-lang="json") #{JSON.stringify(methodhelp.args, null, 4)}
else
:markdown-it
Browse RPC commands from the list. The list is built from the results of the `help` command and organized into sections accordingly.
div.col-md-3
+dismissableInfoAlert("rpcBrowserNoteDismissed", "About RPC Browser...")
.mb-2 This tool lists all RPC commands that your node makes publicly visible. Clicking on any command in the list will display additional information about that command and provide an interactive tool for submitting the command to your node.
.col-md-3
each section, sectionIndex in gethelp
div.card.shadow-sm.mb-3
div.card-body
h4.h6.mb-0 #{section.name}
small (#{section.methods.length})
hr
+contentSection(`${section.name} (${section.methods.length})`)
ol.mb-0.ps-4
each methodX, methodIndex in section.methods
li
a(href=`./rpc-browser?method=${methodX.name}`, style=(methodX.name == method ? "font-weight: bold; font-style: italic;" : false)) #{methodX.name}
ol(style="padding-left: 30px;")
each methodX, methodIndex in section.methods
li
a.font-monospace(href=`./rpc-browser?method=${methodX.name}`, style=(methodX.name == method ? "font-weight: bold; font-style: italic;" : false)) #{methodX.name}

View file

@ -4,19 +4,18 @@ block headContent
title RPC Terminal
block content
div.row
div.col
h1.h3 RPC Terminal
+pageTitle("RPC Terminal")
div.col
if (!config.demoSite && (!config.credentials.rpc || !config.credentials.rpc.rpc))
span(style="float: right;")
a.btn.btn-secondary(href="./disconnect") Disconnect from node
hr
:markdown-it
Use this interactive terminal to send RPC commands to your node. Results will be shown inline. To browse all available RPC commands you can use the [RPC Browser](./rpc-browser).
if (!config.demoSite && (!config.credentials.rpc || !config.credentials.rpc.rpc))
.mb-2
a.btn.btn-secondary(href="./disconnect") Disconnect from node
+dismissableInfoAlert("rpcTermPageNoteDismissed", "About RPC Terminal...")
.mb-2 This tool lets you send custom RPC commands to your node and will display the results below.
.mb-2 To browse all available RPC commands you can use the <a href="./rpc-browser">RPC Browser</a>.
div.card.shadow-sm.mb-3
div.card-body

View file

@ -4,15 +4,14 @@ block headContent
title Search
block content
h1.h3 Search
hr
+pageTitle("Search")
div.card.shadow-sm.mb-huge
div.card-body
form.form.form-inline(method="post", action="./search")
input(type="hidden", name="_csrf", value=csrfToken)
+contentSection
form.form.form-inline(method="post", action="./search")
input(type="hidden", name="_csrf", value=csrfToken)
div.input-group
input.form-control(type="text", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 400px;")
button.btn.btn-primary(type="submit") Search
div.input-group
input.form-control(type="text", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 400px;")
button.btn.btn-primary(type="submit") Search

View file

@ -4,23 +4,19 @@ block headContent
title Terminal
block content
div.row
div.col
h1.h3 Terminal
hr
+pageTitle("Terminal")
:markdown-it
Use this interactive terminal to send RPC commands to your node. Results will be shown inline. To browse all available RPC commands you can use the [RPC Browser](/rpc-browser).
+dismissableInfoAlert
.mb-3 This is an internal developer tool.
div.card.shadow-sm.mb-3
div.card-body
form(id="terminal-form")
.mb-3
label(for="input-cmd") Command
input.form-control(type="text", id="input-cmd", name="cmd")
+contentSection
form(id="terminal-form")
.mb-3
label(for="input-cmd") Command
input.form-control(type="text", id="input-cmd", name="cmd")
input.btn.btn-primary.w-100(type="submit", value="Send")
input.btn.btn-primary.w-100(type="submit", value="Send")
hr

View file

@ -4,72 +4,70 @@ block headContent
title Tools
block content
h1.h3 Tools
hr
+pageTitle("Tools")
div.card.shadow-sm.mb-huge
div.card-body
- var priorityList = config.site.prioritizedToolIdsList;
- var indexLists1Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 1);
- var indexLists2Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 2);
- var indexLists3Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 3);
+contentSection
- var priorityList = config.site.prioritizedToolIdsList;
- var indexLists1Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 1);
- var indexLists2Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 2);
- var indexLists3Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 3);
// xs
div.d-block.d-sm-none(id="tools-1-col")
div.row
each indexList in indexLists1Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
// xs
div.d-block.d-sm-none(id="tools-1-col")
div.row
each indexList in indexLists1Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-start
div.lead
a(href=item.url) #{item.name}
div.float-start
div.lead
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
div(style="padding-left: 39px;")
p #{item.desc}
// sm, md, lg
div.d-none.d-sm-block.d-xl-none(id="tools-2-col")
div.row
each indexList in indexLists2Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
// sm, md, lg
div.d-none.d-sm-block.d-xl-none(id="tools-2-col")
div.row
each indexList in indexLists2Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-start
div.lead
a(href=item.url) #{item.name}
div.float-start
div.lead
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
div(style="padding-left: 39px;")
p #{item.desc}
// xl, xxl
div.d-none.d-xl-block(id="tools-3-col")
div.row
each indexList in indexLists3Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
// xl, xxl
div.d-none.d-xl-block(id="tools-3-col")
div.row
each indexList in indexLists3Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-start.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-start
div.lead
a(href=item.url) #{item.name}
div.float-start
div.lead
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
div(style="padding-left: 39px;")
p #{item.desc}

View file

@ -10,60 +10,38 @@ block headContent
block content
if (!result || !result.getrawtransaction)
h1.h3.word-wrap Transaction
br
small.font-monospace #{txid}
hr
+pageTitle("Transaction", txid, true)
if (block)
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h2.h6.mb-0 Summary
hr
+contentSection("Summary")
+summaryRow(2)
+summaryItem("Block")
a(href=`./block/${block.hash}`) ##{block.height.toLocaleString()}
div.clearfix
div.row
div.summary-table-label Block
div.summary-table-content.font-monospace
a(href=`./block/${block.hash}`) ##{block.height.toLocaleString()}
+summaryItem("Date")
if (block.time)
+timestamp(block.time)
div.row
div.summary-table-label Date
div.summary-table-content.font-monospace
if (block.time)
td.font-monospace
- var timestampHuman = block.time;
include includes/timestamp-human.pug
small.ms-1 utc
else
| ?
- var timeAgoTime = block.time;
span.text-muted.ms-2 (
include includes/time-ago-text.pug
span ago)
div.alert.alert-warning.pb-0(role="alert")
+warningAlert
h6.mb-2 Transaction Details Unavailable
if (blockHeight && global.prunedBlockchain && blockHeight < global.pruneHeight)
- var msgMarkdown = "Blockchain `pruning` is enabled on your node. This setting tells your node that after validating transactions it may discard data that is non-essential for future validation needs.\n\nThe block that contains this transaction has been pruned, meaning that non-essential data has been deleted, including the details of this transaction.";
| !{markdown(msgMarkdown)}
.mb-2 Blockchain <b>pruning</b> is enabled on your node. This setting tells your node that after validating transactions it may discard data that is non-essential for future validation needs.
.mb-0 The block that contains this transaction has been pruned, meaning that non-essential data has been deleted, including the details of this transaction.
else if (!global.txindexAvailable)
| !{markdown(noTxIndexMsg)}
if (result && result.getrawtransaction)
h1.h3.word-wrap Transaction
br
small.font-monospace #{txid}
hr
+pageTitle("Transaction", txid, true, [["coinbase", "This is a coinbase transaction."]])
+pageTabs(["Details", "Scripts", "JSON"])
ul.nav.nav-tabs.mb-3
li.nav-item
a.nav-link.active(data-bs-toggle="tab", href="#tab-details", role="tab") Details
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-scripts", role="tab") Scripts
li.nav-item
a.nav-link(data-bs-toggle="tab", href="#tab-json", role="tab") JSON
- DecimalRounded = Decimal.clone({ precision: 4, rounding: 2 })
@ -81,265 +59,170 @@ block content
- totalOutputValue = totalOutputValue.plus(new Decimal(vout.value));
div.tab-content
div.tab-pane.active(id="tab-details", role="tabpanel")
+pageTab("Details", true)
if (global.specialTransactions && global.specialTransactions[txid])
div.alert.alert-primary.shadow-sm.pb-0
div.float-start(style="width: 55px; height: 55px; font-size: 18px;")
i.fas.fa-certificate.fa-2x(style="margin-top: 10px;")
- var stInfo = global.specialTransactions[txid];
h4.alert-heading.h6.fw-bold #{coinConfig.name} Fun
+funAlert(stInfo.alertBodyHtml, stInfo.summary, stInfo.referenceUrl)
// special transaction info
- var stInfo = global.specialTransactions[txid];
if (stInfo.alertBodyHtml)
p
span !{stInfo.alertBodyHtml}
if (stInfo.referenceUrl && stInfo.referenceUrl.trim().length > 0 && stInfo.alertBodyHtml.indexOf(stInfo.referenceUrl) == -1)
span
a(href=stInfo.referenceUrl) Read more
else
p
span #{stInfo.summary}
if (stInfo.referenceUrl && stInfo.referenceUrl.trim().length > 0)
span
a(href=stInfo.referenceUrl) Read more
.clearfix
.float-end
if (`${userSettings["txPageShowTechDetails"]}` == "false")
a.btn.btn-primary.btn-sm.me-2(href=`./changeSetting?name=txPageShowTechDetails&value=true`, title="Display technical details for this transaction.", data-bs-toggle="tooltip")
i.far.fa-plus-square.me-2
| Technical Details
- var isTxConfirmed = true;
if (!result.getrawtransaction.confirmations || result.getrawtransaction.confirmations == 0)
- isTxConfirmed = false;
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h2.h6.mb-0 Summary
hr
if (isCoinbaseTx)
- var minerInfo = utils.getMinerFromCoinbaseTx(result.getrawtransaction);
div.clearfix
if (!isTxConfirmed)
div.row
div.summary-table-label Status
div.summary-table-content.font-monospace
span.text-danger Unconfirmed
+contentSection("Summary")
+summaryRow((isTxConfirmed ? 2 : 2) + (isCoinbaseTx ? 1 : 2) + (isCoinbaseTx && minerInfo ? 1 : 0))
if (!isTxConfirmed)
+summaryItem("Status")
span.text-danger Unconfirmed
div.row
div.summary-table-label
span.border-dotted(title="Whether this unconfirmed transaction is replaceable using replace-by-fee (RBF)", data-bs-toggle="tooltip") RBF
div.summary-table-content.font-monospace
- var replaceable = false;
each vin, vinIndex in result.getrawtransaction.vin
// ref: https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#summary
if (vin.sequence < 4294967295 - 1)
- replaceable = true;
+summaryItem("RBF", "Whether this unconfirmed transaction is replaceable using Replace-By-Fee.")
- var replaceable = false;
each vin, vinIndex in tx.vin
// ref: https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#summary
if (vin.sequence < 4294967295 - 1)
- replaceable = true;
if (replaceable)
span.text-success Yes
else if (mempoolDetails.ancestors.length > 0)
span.border-dotted(title="RBF may be signaled explicitly or implicitly. This transaction does not explicitly opt in, but if any of its unconfirmed ancestor transactions are replaceable then this one is too.", data-bs-toggle="tooltip") No explicit opt-in
a.ms-2(href="https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#summary", data-bs-toggle="tooltip", title="More info about RBF", target="_blank")
i.fas.fa-info-circle
else
span No
if (replaceable)
span.text-success Yes
else if (mempoolDetails.ancestors.length > 0)
span.border-dotted(title="RBF may be signaled explicitly or implicitly. This transaction does not explicitly opt in, but if any of its unconfirmed ancestor transactions are replaceable then this one is too.", data-bs-toggle="tooltip") No explicit opt-in
a.ms-2(href="https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#summary", data-bs-toggle="tooltip", title="More info about RBF", target="_blank")
i.fas.fa-info-circle
else
span No
if (isTxConfirmed)
div.row
div.summary-table-label Block
div.summary-table-content.font-monospace
a(href=`./block/${result.getrawtransaction.blockhash}`) ##{result.getblock.height.toLocaleString()}
else
+summaryItem("Block")
a(href=`./block/${result.getrawtransaction.blockhash}`) #{result.getblock.height.toLocaleString()}
small.ms-2.text-muted
| (
+confirmations(tx.confirmations)
| )
+summaryItem("Date")
if (tx.time)
td.font-monospace
+timestamp(result.getrawtransaction.time)
if (isTxConfirmed)
div.row
div.summary-table-label Date
div.summary-table-content.font-monospace
if (result.getrawtransaction.time)
td.font-monospace
- var timestampHuman = result.getrawtransaction.time;
include includes/timestamp-human.pug
small.ms-1 utc
- var timeAgoTime = result.getrawtransaction.time;
span.text-muted.ms-2 (
include includes/time-ago-text.pug
span ago)
div.row
div.summary-table-label Version
div.summary-table-content.font-monospace #{result.getrawtransaction.version}
div.row
div.summary-table-label Size
div.summary-table-content.font-monospace
if (result.getrawtransaction.vsize != result.getrawtransaction.size)
span #{result.getrawtransaction.vsize.toLocaleString()}
small vB
br
span.text-muted (#{result.getrawtransaction.size.toLocaleString()}
small B
span )
if (isCoinbaseTx)
if (minerInfo)
+summaryItem("Miner")
if (minerInfo.identifiedBy)
small.badge.bg-primary(data-bs-toggle="tooltip", title=`Identified by: ${minerInfo.identifiedBy}`) #{minerInfo.name}
else
span #{result.getrawtransaction.size.toLocaleString()}
small B
small.badge.bg-primary #{minerInfo.name}
if (result.getrawtransaction.locktime > 0)
div.row
div.summary-table-label Locktime
div.summary-table-content.font-monospace
if (result.getrawtransaction.locktime < 500000000)
span Spendable in block
a(href=`./block-height/${result.getrawtransaction.locktime}`) ##{result.getrawtransaction.locktime.toLocaleString()}
span or later
a(href="https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number", data-bs-toggle="tooltip", title="More info about locktime", target="_blank")
i.fas.fa-info-circle
else
span Spendable after #{moment.utc(new Date(result.getrawtransaction.locktime * 1000)).format("Y-MM-DD HH:mm:ss")}
small utc
a(href="https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number", data-bs-toggle="tooltip", title="More info about locktime", target="_blank")
i.fas.fa-info-circle
+summaryItem("Fees Collected", "The aggregate fees collected by the miner that created this coinbase transaction.")
- var currencyValue = new Decimal(totalOutputValue).minus(totalInputValue);
include includes/value-display.pug
if (isTxConfirmed)
div.row
div.summary-table-label Confirmations
div.summary-table-content.font-monospace
if (result.getrawtransaction.confirmations < 6)
span.fw-bold.text-warning #{result.getrawtransaction.confirmations.toLocaleString()}
a(data-bs-toggle="tooltip", title="Fewer than 6 confirmations is generally considered 'unsettled' for high-value transactions. The applicability of this guidance may vary.")
i.fas.fa-unlock-alt
else
+summaryItem("Fee Rate", null, "sat/vB")
if (!mempoolDetails && !global.txindexAvailable)
span.border-dotted(title="Determining the fee of confirmed transactions requires txindex to be enabled", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger.me-2
| ?
else
span.fw-bold.text-success #{result.getrawtransaction.confirmations.toLocaleString()}
a(data-bs-toggle="tooltip", title="6 confirmations is generally considered 'settled'. High-value transactions may require more; low-value transactions may require less.")
i.fas.fa-lock
if (result.getrawtransaction.vin[0].coinbase)
div.row
div.summary-table-label Fees Collected
div.summary-table-content.font-monospace
- var currencyValue = new Decimal(totalOutputValue).minus(totalInputValue);
include includes/value-display.pug
- var blockRewardMax = coinConfig.blockRewardFunction(result.getblock.height, activeBlockchain);
if (parseFloat(totalOutputValue) < parseFloat(blockRewardMax))
div.row
div.summary-table-label
span.border-dotted(data-bs-toggle="tooltip" title="The miner of this transaction's block failed to collect this value. As a result, it is permanently lost.") Value Destroyed
div.summary-table-content.font-monospace.text-danger
- var currencyValue = new Decimal(blockRewardMax).minus(totalOutputValue);
include includes/value-display.pug
- var minerInfo = utils.getMinerFromCoinbaseTx(result.getrawtransaction);
if (minerInfo)
div.row
div.summary-table-label Miner
div.summary-table-content.font-monospace
if (minerInfo.identifiedBy)
small.data-tag.bg-primary(data-bs-toggle="tooltip", title=`Identified by: ${minerInfo.identifiedBy}`) #{minerInfo.name}
else
small.data-tag.bg-primary #{minerInfo.name}
else if (!mempoolDetails && !global.txindexAvailable)
div.row
div.summary-table-label Fee Paid
div.summary-table-content.font-monospace
span.border-dotted(title="Determining the fee of confirmed transactions requires txindex to be enabled", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger.me-2
| ?
else
- var feePaid = mempoolDetails ? new Decimal(mempoolDetails.entry.fees.base) : new Decimal(totalInputValue).minus(totalOutputValue);
div.row
div.summary-table-label Fee Paid
div.summary-table-content.font-monospace
- var currencyValue = feePaid;
include includes/value-display.pug
if (feePaid > 0 && totalInputValue > 0)
- var item1 = utils.formatCurrencyAmount(totalInputValue, currencyFormatType);
- var item2 = utils.formatCurrencyAmount(totalOutputValue, currencyFormatType);
span.ms-2(title=`${item1.simpleVal} - ${item2.simpleVal}`, data-bs-toggle="tooltip")
i.fas.fa-ellipsis-h
if (feePaid > 0)
div.row
div.summary-table-label Fee Rate
div.summary-table-content.font-monospace
if (result.getrawtransaction.vsize != result.getrawtransaction.size)
span #{utils.addThousandsSeparators(new DecimalRounded(feePaid).dividedBy(result.getrawtransaction.vsize).times(100000000))}
small sat/vB
br
span.text-muted (#{utils.addThousandsSeparators(new DecimalRounded(feePaid).dividedBy(result.getrawtransaction.size).times(100000000))}
small sat/B
span )
else
span #{utils.addThousandsSeparators(new DecimalRounded(feePaid).dividedBy(result.getrawtransaction.size).times(100000000))}
small sat/B
- var daysDestroyed = new Decimal(0);
each xInput, xInputIndex in result.getrawtransaction.vin
- var vout = null;
if (result.txInputs && result.txInputs[xInputIndex])
- var vout = result.txInputs[xInputIndex];
else
- var feePaid = mempoolDetails ? new Decimal(mempoolDetails.entry.fees.base) : new Decimal(totalInputValue).minus(totalOutputValue);
if (vout.utxoTime)
- var dt = result.getrawtransaction.time - vout.utxoTime;
- var dtDays = dt / 60 / 60 / 24;
- daysDestroyed = daysDestroyed.plus(new Decimal(vout.value).times(dtDays));
span #{utils.addThousandsSeparators(new DecimalRounded(feePaid).dividedBy(tx.vsize).times(100000000))}
if (parseInt(daysDestroyed) > 0)
div.row
div.summary-table-label
span.border-dotted(title=`The sum of all input values multiplied by the number of days that input's UTXO existed before being spent. Large values can sometimes be interesting, indicating the spending of long-dormant coins. A 1 ${coinConfig.ticker} UTXO spent after 7 days dormant results in 7 days destroyed, as does a 7 ${coinConfig.ticker} UTXO spent after 1 day dormant.`, data-bs-toggle="tooltip") Days Destroyed
div.summary-table-content.font-monospace
span #{utils.addThousandsSeparators(daysDestroyed.toDP(daysDestroyed > 1 ? 0 : 1))}
small.ms-1.border-dotted(title=`${coinConfig.name} Days`, data-bs-toggle="tooltip") #{coinConfig.ticker.toLowerCase().substring(0, 1)}d
+summaryItem("Total Fee")
if (!mempoolDetails && !global.txindexAvailable)
span.border-dotted(title="Determining the fee of confirmed transactions requires txindex to be enabled", data-bs-toggle="tooltip")
i.fas.fa-backspace.text-danger.me-2
| ?
if (result.getrawtransaction.vin[0].coinbase)
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6.mb-0 Coinbase
hr
h6 Hex
div.bg-light.px-2.py-2.mb-3.border
span.font-monospace.word-wrap #{result.getrawtransaction.vin[0].coinbase}
h6 Decoded
div.bg-light.px-2.py-2.border
span.text-monospace.word-wrap #{utils.hex2ascii(result.getrawtransaction.vin[0].coinbase)}
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h2.h6.mb-0
span #{result.getrawtransaction.vin.length.toLocaleString()}
if (result.getrawtransaction.vin.length == 1)
span Input
else
span Inputs
- var feePaid = mempoolDetails ? new Decimal(mempoolDetails.entry.fees.base) : new Decimal(totalInputValue).minus(totalOutputValue);
- var currencyValue = feePaid;
include includes/value-display.pug
span.mx-2.text-muted.text-small /
span #{result.getrawtransaction.vout.length.toLocaleString()}
if (result.getrawtransaction.vout.length == 1)
span Output
else
span Outputs
hr
- var daysDestroyed = new Decimal(0);
each xInput, xInputIndex in tx.vin
- var vout = null;
if (result.txInputs && result.txInputs[xInputIndex])
- var vout = result.txInputs[xInputIndex];
if (vout.utxoTime)
- var dt = result.getrawtransaction.time - vout.utxoTime;
- var dtDays = dt / 60 / 60 / 24;
- daysDestroyed = daysDestroyed.plus(new Decimal(vout.value).times(dtDays));
+contentSection("Technical Details", true, "txPageShowTechDetails")
if (isTxConfirmed)
- var blockRewardMax = coinConfig.blockRewardFunction(result.getblock.height, activeBlockchain);
- var valueDestroyed = (isCoinbaseTx && parseFloat(totalOutputValue) < parseFloat(blockRewardMax));
+summaryRow(2 + (tx.weight ? 1 : 0) + (valueDestroyed ? 1 : 0) + (tx.locktime > 0 ? 1 : 0) + (parseInt(daysDestroyed) > 0 ? 1 : 0))
+summaryItem("Version")
| #{result.getrawtransaction.version}
+summaryItem("Size")
span #{tx.vsize.toLocaleString()}
small.ms-1 vB
if (tx.weight)
+summaryItem("Weight", null, "wu", "Weight Units")
span #{tx.weight.toLocaleString()}
if (valueDestroyed)
+summaryItem("Value Destroyed", "The miner of this transaction's block failed to collect this value. As a result, it is permanently lost.")
- var currencyValue = new Decimal(blockRewardMax).minus(totalOutputValue);
span.text-danger
include includes/value-display.pug
if (tx.locktime > 0)
+summaryItem("Locktime")
if (tx.locktime < 500000000)
span.me-1 Spendable:
a(href=`./block-height/${result.getrawtransaction.locktime}`) #{result.getrawtransaction.locktime.toLocaleString()}
span +
a.ms-2(href="https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number", data-bs-toggle="tooltip", title="Read more about locktime", target="_blank")
i.fas.fa-external-link-alt.small
else
span Spendable: #{moment.utc(new Date(result.getrawtransaction.locktime * 1000)).format("Y-MM-DD HH:mm:ss")}
small.ms-1 utc
a.ms-2(href="https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number", data-bs-toggle="tooltip", title="Read more about locktime", target="_blank")
i.fas.fa-external-link-alt.fs-6
if (parseInt(daysDestroyed) > 0)
- var daysDestroyedDesc = `The sum of all input values multiplied by the number of days that input's UTXO existed before being spent. Large values can sometimes be interesting, indicating the spending of long-dormant coins. A 1 ${coinConfig.ticker} UTXO spent after 7 days dormant results in 7 days destroyed, as does a 7 ${coinConfig.ticker} UTXO spent after 1 day dormant.`;
+summaryItem("Days Destroyed", daysDestroyedDesc, `${coinConfig.ticker.toLowerCase().substring(0, 1)}d`, `${coinConfig.name} Days`)
| #{utils.addThousandsSeparators(daysDestroyed.toDP(daysDestroyed > 1 ? 0 : 1))}
- var inputsOutputsSectionTitle = `${tx.vin.length.toLocaleString()} Input${tx.vin.length == 1 ? "" : "s"}, ${tx.vout.length.toLocaleString()} Output${tx.vout.length == 1 ? "" : "s"}`;
+contentSection(inputsOutputsSectionTitle)
- var tx = result.getrawtransaction;
- var txInputs = result.txInputs;
- var blockHeight = -1;
if (result && result.getblock)
- blockHeight = result.getblock.height;
include includes/transaction-io-details.pug
- var tx = result.getrawtransaction;
- var txInputs = result.txInputs;
- var blockHeight = -1;
if (result && result.getblock)
- blockHeight = result.getblock.height;
include includes/transaction-io-details.pug
if (mempoolDetails)
if (mempoolDetails.ancestors.length > 0)
@ -374,86 +257,103 @@ block content
li
a.font-monospace(href=`./tx/${descendantTxid}`) #{descendantTxid}
div.tab-pane(id="tab-scripts", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6.mb-0 Input Scripts
hr
table.table.table-striped
thead
+pageTab("Scripts")
+contentSection("Input Scripts")
table.table.table-borderless.table-striped
thead
tr
th(style="width: 50px;")
th Input
tbody
each vin, vinIndex in result.getrawtransaction.vin
tr
th(style="width: 50px;")
th Script Sig (asm)
tbody
each vin, vinIndex in result.getrawtransaction.vin
tr
th.font-monospace
a(data-bs-toggle="tooltip", title=`Input #${vinIndex}`, style="white-space: nowrap;")
span >
span.fw-normal ##{vinIndex.toLocaleString()}
th.font-monospace
a.badge.card-highlight.border.fw-normal(data-bs-toggle="tooltip", title=`Input #${vinIndex}`, style="white-space: nowrap;")
span.me-1 &gt;
| #{vinIndex.toLocaleString()}
td.word-wrap.text-break.font-monospace
if (vin.scriptSig && vin.scriptSig.asm)
span #{vin.scriptSig.asm}
td.word-wrap.text-break.font-monospace
if (vin.coinbase)
div.font-monospace(style="line-height: 1.75em;")
.badge.bg-success.me-2 coinbase
br
span.font-data #{vin.coinbase}
br
span.fw-bold.me-2 Ascii
span.font-plaintext #{utils.hex2ascii(vin.coinbase)}
else if (vin.coinbase)
div.font-monospace(style="line-height: 1.75em;")
small.rounded.bg-primary.text-white.px-2.py-1.me-2 coinbase
br
span #{vin.coinbase}
br
span.text-muted (decoded) #{utils.hex2ascii(vin.coinbase)}
if (vin.scriptSig && vin.scriptSig.asm)
.mb-2
.mb-2
span.badge.bg-dark.me-2 scriptSig
span.font-data #{vin.scriptSig.asm}
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6.mb-0 Output Scripts
hr
table.table.table-striped
thead
if (vin.txinwitness && vin.txinwitness.length > 0)
.mb-2
span.badge.bg-dark.me-2 witness
each witnessItem in vin.txinwitness
span.font-data.me-3 #{witnessItem}
+contentSection("Output Scripts")
table.table.table-borderless.table-striped
thead
tr
th(style="width: 50px;")
th Script Pub Key (asm)
tbody
each vout, voutIndex in result.getrawtransaction.vout
tr
th(style="width: 50px;")
th Script Pub Key (asm)
tbody
each vout, voutIndex in result.getrawtransaction.vout
tr
th.font-monospace
a(data-bs-toggle="tooltip", title=`Output #${(voutIndex + 1)}`, style="white-space: nowrap;")
span <
span.fw-normal ##{voutIndex.toLocaleString()}
th.font-monospace
a.badge.card-highlight.border.fw-normal(data-bs-toggle="tooltip", title=`Output #${(voutIndex + 1)}`, style="white-space: nowrap;")
span.me-1 &lt;
span.fw-normal #{voutIndex.toLocaleString()}
td.font-monospace.word-wrap
if (vout.scriptPubKey && vout.scriptPubKey.asm)
span #{vout.scriptPubKey.asm}
if (vout.scriptPubKey.asm.startsWith("OP_RETURN"))
br
span.text-muted (decoded) #{utils.hex2ascii(vout.scriptPubKey.asm)}
td.font-monospace.word-wrap
if (vout.scriptPubKey && vout.scriptPubKey.asm)
- var chunks = vout.scriptPubKey.asm.split(" ");
each chunk in chunks
span.me-2
if (chunk.startsWith("OP_"))
span.badge.bg-primary #{chunk}
else
span.font-data #{chunk}
div.tab-pane(id="tab-json", role="tabpanel")
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transaction
hr
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(result.getrawtransaction, null, 4)}
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 UTXOs
hr
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(utxos, null, 4)}
if (vout.scriptPubKey.asm.startsWith("OP_RETURN"))
br
span.fw-bold.me-2 Ascii
span.font-data #{utils.hex2ascii(vout.scriptPubKey.asm)}
+pageTab("JSON")
- var pillTabs = ["Transaction", "UTXOs"];
if (mempoolDetails)
h3.h5 Mempool Details
div.highlight
pre
code.json.bg-light(data-lang="json") #{JSON.stringify(mempoolDetails, null, 4)}
- pillTabs.push("Mempool Details");
+pillTabs(pillTabs)
.tab-content
+pageTab("Transaction", true)
+contentSection("Transaction")
pre
code.text-json.json.bg-body(data-lang="json") #{JSON.stringify(result.getrawtransaction, null, 4)}
+pageTab("UTXOs")
+contentSection("UTXOs")
pre
code.text-json.json.bg-body(data-lang="json") #{JSON.stringify(utxos, null, 4)}
if (mempoolDetails)
+pageTab("Mempool Details")
+contentSection("Mempool Details")
pre
code.json.bg-body(data-lang="json") #{JSON.stringify(mempoolDetails, null, 4)}
//pre #{JSON.stringify(result.txInputs, null, 4)}

View file

@ -4,12 +4,11 @@ block headContent
title Transaction Stats
block content
h1.h3 Transaction Stats
hr
+pageTitle("Transaction Stats")
if (false)
pre
code.json.bg-light #{JSON.stringify(txStatResults, null, 4)}
code.json #{JSON.stringify(txStatResults, null, 4)}
if (true)
if (false)
@ -18,109 +17,90 @@ block content
if (true)
div.row
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transactions, 24hr
hr
+contentSection("1day Transactions")
script var txCountDataDay = [];
each item, index in txStatsDay.txCounts
script txCountDataDay.push({x:#{item.x}, y:#{item.y}});
script var txCountDataDay = [];
each item, index in txStatsDay.txCounts
script txCountDataDay.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphDay", dataVar:"txCountDataDay", labels:txStatsDay.txLabels, title:"Transactions, 24hr", xaxisTitle:"Block", xaxisStep:5, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
- var graphData = {id:"graphDay", dataVar:"txCountDataDay", labels:txStatsDay.txLabels, title:"Transactions, 24hr", xaxisTitle:"Block", xaxisStep:5, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transactions, 7day
hr
+contentSection("7day Transactions")
script var txCountDataWeek = [];
each item, index in txStatsWeek.txCounts
script txCountDataWeek.push({x:#{item.x}, y:#{item.y}});
script var txCountDataWeek = [];
each item, index in txStatsWeek.txCounts
script txCountDataWeek.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphWeek", dataVar:"txCountDataWeek", labels:txStatsWeek.txLabels, title:"Transactions, 7day", xaxisTitle:"Block", xaxisStep:100, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
- var graphData = {id:"graphWeek", dataVar:"txCountDataWeek", labels:txStatsWeek.txLabels, title:"Transactions, 7day", xaxisTitle:"Block", xaxisStep:100, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transactions, 30day
hr
+contentSection("30day Transactions")
script var txCountDataMonth = [];
each item, index in txStatsMonth.txCounts
script txCountDataMonth.push({x:#{item.x}, y:#{item.y}});
script var txCountDataMonth = [];
each item, index in txStatsMonth.txCounts
script txCountDataMonth.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphMonth", dataVar:"txCountDataMonth", labels:txStatsMonth.txLabels, title:"Transactions, 30day", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
- var graphData = {id:"graphMonth", dataVar:"txCountDataMonth", labels:txStatsMonth.txLabels, title:"Transactions, 30day", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Transactions, All time
hr
+contentSection("All-Time Transactions")
script var txCountDataAlltime = [];
each item, index in txStats.txCounts
script txCountDataAlltime.push({x:#{item.x}, y:#{item.y}});
script var txCountDataAlltime = [];
each item, index in txStats.txCounts
script txCountDataAlltime.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphAlltime", dataVar:"txCountDataAlltime", labels:txStatsMonth.txLabels, title:"Transactions, All time", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
- var graphData = {id:"graphAlltime", dataVar:"txCountDataAlltime", labels:txStatsMonth.txLabels, title:"Transactions, All time", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Count"};
include ./includes/line-graph.pug
div.row
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Tx Rate, 24hr
hr
+contentSection("1day Tx Rate")
script var txRateDataDay = [];
each item, index in txStatsDay.txRates
script txRateDataDay.push({x:#{item.x}, y:#{item.y}});
script var txRateDataDay = [];
each item, index in txStatsDay.txRates
script txRateDataDay.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphRateDay", dataVar:"txRateDataDay", labels:txStatsDay.txLabels, title:"Tx Rate, 24hr", xaxisTitle:"Block", xaxisStep:5, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug
- var graphData = {id:"graphRateDay", dataVar:"txRateDataDay", labels:txStatsDay.txLabels, title:"Tx Rate, 24hr", xaxisTitle:"Block", xaxisStep:5, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Tx Rate, 7day
hr
+contentSection("7day Tx Rate")
script var txRateDataWeek = [];
each item, index in txStatsWeek.txRates
script txRateDataWeek.push({x:#{item.x}, y:#{item.y}});
script var txRateDataWeek = [];
each item, index in txStatsWeek.txRates
script txRateDataWeek.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphRateWeek", dataVar:"txRateDataWeek", labels:txStatsWeek.txLabels, title:"Tx Rate, 7day", xaxisTitle:"Block", xaxisStep:100, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug
- var graphData = {id:"graphRateWeek", dataVar:"txRateDataWeek", labels:txStatsWeek.txLabels, title:"Tx Rate, 7day", xaxisTitle:"Block", xaxisStep:100, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Tx Rate, 30day
hr
+contentSection("30day Tx Rate")
script var txRateDataMonth = [];
each item, index in txStatsMonth.txRates
script txRateDataMonth.push({x:#{item.x}, y:#{item.y}});
script var txRateDataMonth = [];
each item, index in txStatsMonth.txRates
script txRateDataMonth.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphRateMonth", dataVar:"txRateDataMonth", labels:txStatsMonth.txLabels, title:"Tx Rate, 30day", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug
- var graphData = {id:"graphRateMonth", dataVar:"txRateDataMonth", labels:txStatsMonth.txLabels, title:"Tx Rate, 30day", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug
div.col-lg-6
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Tx Rate, All time
hr
+contentSection("All-Time Tx Rate")
script var txRateDataAlltime = [];
each item, index in txStats.txRates
script txRateDataAlltime.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphRateAlltime", dataVar:"txRateDataAlltime", labels:txStatsMonth.txLabels, title:"Tx Rate, All time", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug
script var txRateDataAlltime = [];
each item, index in txStats.txRates
script txRateDataAlltime.push({x:#{item.x}, y:#{item.y}});
- var graphData = {id:"graphRateAlltime", dataVar:"txRateDataAlltime", labels:txStatsMonth.txLabels, title:"Tx Rate, All time", xaxisTitle:"Block", xaxisStep:500, yaxisTitle:"Tx Per Sec"};
include ./includes/line-graph.pug

View file

@ -1,55 +0,0 @@
extends layout
block headContent
title Unconfirmed Transactions
block content
h1.h3 Unconfirmed Transactions
hr
if (false)
pre
code #{JSON.stringify(mempoolDetails.txCount, null, 4)}
if (mempoolDetails)
- var txCount = mempoolDetails.txCount;
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0
if (txCount == 1)
span 1 Transaction
else
span #{txCount.toLocaleString()} Transactions
hr
each tx, txIndex in mempoolDetails.transactions
div.card.shadow-sm.mb-3
div.card-header.font-monospace
if (tx && tx.txid)
span ##{(txIndex + offset).toLocaleString()}
span &ndash;
a(href=`./tx/${tx.txid}`) #{tx.txid}
div.card-body
- var txInputs = mempoolDetails.txInputsByTransaction[tx.txid];
- var blockHeight = -1;
include includes/transaction-io-details.pug
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 + "&sort=" + sort);
- }
hr
include includes/pagination.pug
else
p No unconfirmed transactions found