lots of minor frontend/code cleanup:

-mixin for txList
-fee/feeRate badge on tx lists (block, address, mempool browse)
-change to single "net balance change" badge on address tx list
-fix minor issues with sats value display
-cleanup minor issue with "time ago" display tooltip
-cleanup text coloring for various badge mixins
-fix display of negative large values
This commit is contained in:
Dan Janosik 2023-06-13 11:34:08 -04:00
parent 6d0868e8c6
commit e8eeb184e1
No known key found for this signature in database
GPG key ID: 70C0B166321C0AF8
6 changed files with 475 additions and 391 deletions

View file

@ -871,7 +871,7 @@ function formatLargeNumber(n, decimalPlaces) {
let item = exponentScales[i];
let fraction = new Decimal(n / item.val);
if (fraction >= 1) {
if (Math.abs(fraction) >= 1) {
return [fraction.toDP(decimalPlaces), item];
}
}
@ -891,7 +891,7 @@ function formatLargeNumberSignificant(n, significantDigits) {
let item = exponentScales[i];
let fraction = new Decimal(n / item.val);
if (fraction >= 1) {
if (Math.abs(fraction) >= 1) {
return [fraction.toDP(Math.max(0, significantDigits - `${Math.floor(fraction)}`.length)), item];
}
}

View file

@ -159,6 +159,11 @@ mixin txList(visibleTxs, txCount, limit, offset, inputsByTxid, options={})
- var tx_height_param = (!global.txindexAvailable && txBlockHeight) ? `@${txBlockHeight}` : '';
- var txInputs = inputsByTxid[tx.txid];
- var blockHeight = txBlockHeight != undefined ? txBlockHeight : -1;
- var totalIOValues = utils.getTxTotalInputOutputValues(tx, txInputs, blockHeight);
.border.bg-content.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
@ -179,6 +184,7 @@ mixin txList(visibleTxs, txCount, limit, offset, inputsByTxid, options={})
- var hasInputAddresses = false;
- var coinbaseTx = false;
if (tx && tx.vin && inputsByTxid && inputsByTxid[tx.txid])
- var txInputs = inputsByTxid[tx.txid];
each txVin, txVinIndex in tx.vin
@ -190,6 +196,9 @@ mixin txList(visibleTxs, txCount, limit, offset, inputsByTxid, options={})
if (voutAddresses.length > 0)
- hasInputAddresses = true;
else
- coinbaseTx = true;
if (hasInputAddresses)
.float-end
.d-block.d-sm-none
@ -199,24 +208,80 @@ mixin txList(visibleTxs, txCount, limit, offset, inputsByTxid, options={})
.float-start.float-sm-end.mt-n2.mt-sm-0.mb-2.mb-sm-0
.me-5.me-sm-n2
- let txFee = null;
- let txFeeRate = null;
if (options.mempoolDetailsByTxid && options.mempoolDetailsByTxid[tx.txid])
- var mempoolDetails = options.mempoolDetailsByTxid[tx.txid].entry;
- var mempoolDetailsFee = ((mempoolDetails.fees && mempoolDetails.fees.modified) ? mempoolDetails.fees.modified : (mempoolDetails.fee ? mempoolDetails.fee : 0));
- var feeRate = new Decimal(mempoolDetailsFee).times(coinConfig.baseCurrencyUnit.multiplier).dividedBy(mempoolDetails.vsize);
+lightBadge
| #{parseInt(feeRate).toLocaleString()}
span.text-tiny.ms-tiny sat/vB
- txFee = new Decimal(mempoolDetailsFee);
- txFeeRate = txFee.times(coinConfig.baseCurrencyUnit.multiplier).dividedBy(mempoolDetails.vsize);
if (!coinbaseTx && totalIOValues.input && totalIOValues.output)
- txFee = new Decimal(totalIOValues.input).minus(totalIOValues.output);
- txFeeRate = txFee.dividedBy(tx.vsize).times(coinConfig.baseCurrencyUnit.multiplier);
if (txFee && txFee > 0)
+infoBadge
span.border-dotted.fw-bold.me-1(title="The miner fee (and fee rate) paid by this transaction. The fee equals the difference between the total input value and the total output value.", data-bs-toggle="tooltip") fee
+_valueDisplaySat(txFee, 1000)
span.ms-2 (
| #{parseInt(txFee.dividedBy(tx.vsize).times(coinConfig.baseCurrencyUnit.multiplier)).toLocaleString()}
span.text-tiny.ms-1 sat/vB
span )
else if (Object.keys(txInputs).length < tx.vin.length)
+infoBadge
span(data-bs-toggle="tooltip", title="Data truncated by performance settings. See transaction details for fee info.")
span.fw-light …
- let addrGain = null;
- let addrLoss = null;
if (options.addrGainsByTxid && options.addrGainsByTxid[tx.txid])
+successBadge
span.me-tiny +
+valueDisplay(options.addrGainsByTxid[tx.txid])
- addrGain = new Decimal(options.addrGainsByTxid[tx.txid]);
if (false)
+successBadge
span +
+valueDisplay(options.addrGainsByTxid[tx.txid])
if (options.addrLossesByTxid && options.addrLossesByTxid[tx.txid])
span.badge.bg-danger.me-2
span.me-tiny -
+valueDisplay(options.addrLossesByTxid[tx.txid])
- addrLoss = new Decimal(options.addrLossesByTxid[tx.txid]);
if (false)
span.badge.bg-danger.me-2
span -
+valueDisplay(options.addrLossesByTxid[tx.txid])
- let addrDelta = null;
if (addrGain && addrLoss)
- addrDelta = addrGain.minus(addrLoss);
else if (addrGain)
- addrDelta = addrGain;
else if (addrLoss)
- addrDelta = addrLoss.times(-1);
if (addrDelta)
+darkBadge
span.border-dotted.fw-bold.me-1(title="Net change to the balance of the current address caused by this transaction", data-bs-toggle="tooltip") net
if (addrDelta > 0)
span.text-success
span +
+valueDisplay(addrDelta)
else
span.text-danger
span -
+valueDisplay(addrDelta.times(-1))
if (options.addrGainsByTxid && options.addrLossesByTxid && !options.addrGainsByTxid[tx.txid] && !options.addrLossesByTxid[tx.txid])
span.badge.bg-danger.me-2(data-bs-toggle="tooltip", title="See transaction details for spent-from-this-address amount")
@ -249,7 +314,7 @@ mixin txList(visibleTxs, txCount, limit, offset, inputsByTxid, options={})
+darkBadge
i.bi-clock.me-1
span.fw-light(title=titleStr, data-bs-toggle="tooltip")
span.fw-light
+timeAgo(tx.time, {oneElement:true})
@ -258,14 +323,7 @@ mixin txList(visibleTxs, txCount, limit, offset, inputsByTxid, options={})
.d-none.d-lg-block.border-end.mt-n4.mb-2(style="border-color: currentColor !important; height: 10px; width: 50%; opacity: 0.25;")
div
- var txInputs = inputsByTxid[tx.txid];
- var blockHeight = txBlockHeight != undefined ? txBlockHeight : -1;
- options.maxTxOutputDisplayCount = maxTxOutputDisplayCount;
if (options.highlightAddress)
- var txIOHighlightAddress = options.highlightAddress;
include ./transaction-io-details.pug
+txIoDetails(tx, txInputs, totalIOValues, blockHeight, {highlightAddress:options.highlightAddress, maxTxOutputDisplayCount:maxTxOutputDisplayCount})
if (txIndex < (visibleTxs.length - 1) || txCount > limit)
.pb-4.pb-md-3.bg-tx-separator
@ -336,16 +394,17 @@ mixin _valueDisplayBtc(val, options={})
mixin _valueDisplaySat(val, summarizeMin=Infinity, summarizeDecimals=3)
- var parts = utils.formatCurrencyAmount(val, localCurrencyDisplay ? userSettings.localCurrency : userSettings.displayCurrency);
- var localCurrencyDisplay = userSettings.displayCurrency == "local";
- var parts = utils.formatCurrencyAmount(val, "sat");
//span #{JSON.stringify(parts)}
if (parts.intVal >= summarizeMin)
if (Math.abs(parts.intVal) >= summarizeMin)
//span #{JSON.stringify(parts)}
- var largeNumberData = utils.formatLargeNumberSignificant(parts.intVal, summarizeDecimals >= 0 ? summarizeDecimals : 3);
//span #{JSON.stringify(largeNumberData)} #{parts.intVal}
span(title=parts.intVal.toLocaleString(), data-bs-toggle="tooltip") #{largeNumberData[0]}
span.ms-1 #{largeNumberData[1].textDesc}
span #{largeNumberData[1].abbreviation}
else
span(class=(localCurrencyDisplay ? "border-dotted" : false), title=(localCurrencyDisplay ? `${utils.formatCurrencyAmount(val, coinConfig.defaultCurrencyUnit.name.toLowerCase()).simpleVal} BTC` : false), data-bs-toggle=(localCurrencyDisplay ? "tooltip" : false)) #{parts.intVal.toLocaleString()}
@ -368,7 +427,7 @@ mixin _valueDisplayLocal(val, summarizeMin=Infinity, summarizeDecimals=3)
//span #{JSON.stringify(parts)}
span.fw-light.me-tiny #{global.currencySymbols[userSettings.localCurrency]}
if (parts.intVal >= summarizeMin)
if (Math.abs(parts.intVal) >= summarizeMin)
- var largeNumberData = utils.formatLargeNumberSignificant(parts.intVal, summarizeDecimals >= 0 ? summarizeDecimals : 3);
//span #{JSON.stringify(largeNumberData)} #{parts.intVal}
span.border-dotted(title=`${global.currencySymbols[userSettings.localCurrency]}${parts.intVal.toLocaleString()}<br/>${utils.formatCurrencyAmount(val, coinConfig.defaultCurrencyUnit.name.toLowerCase()).simpleVal} BTC`, data-bs-toggle="tooltip", data-bs-html="true")
@ -441,8 +500,13 @@ mixin confirmations(confirmations, abbreviated=true)
mixin bodyBgBadge(text)
span.small.badge.bg-body.border.border-card-highlight-badge.text-reset.mb-tiny.me-1 #{text ? text : ""}
if (block)
block
mixin cardHighlightBadge(text)
span.small.badge.bg-card-highlight-badge.border.border-card-highlight-badge.text-reset.mb-tiny.me-1 #{text ? text : ""}
span.small.badge.card-highlight.border.border-card-highlight-badge.text-reset.mb-tiny.me-1 #{text ? text : ""}
if (block)
block
@ -454,7 +518,7 @@ mixin hexDataDisplay(hex, order="utf8,ascii,hex")
.d-flex.flex-row
.d-inline-block.small.border.rounded-1.p-1.px-2.card-highlight.word-wrap
+cardHighlightBadge(items[0])
+bodyBgBadge(items[0])
span.text-tiny.d-inline.d-sm-none #{utils.formatHex(hex, items[0])}
span.small.d-none.d-sm-inline #{utils.formatHex(hex, items[0])}
@ -468,7 +532,7 @@ mixin hexDataDisplay(hex, order="utf8,ascii,hex")
if (itemIndex > 1)
br
+cardHighlightBadge(item)
+bodyBgBadge(item)
span.text-tiny.d-inline.d-sm-none #{utils.formatHex(hex, item)}
span.small.d-none.d-sm-inline #{utils.formatHex(hex, item)}
@ -566,7 +630,8 @@ mixin timeAgo(timeAgoTime, options={oneElement:false, agoText:true})
if (options && "hideHoverText" in options)
- hideHoverText = options.hideHoverText;
- let timeAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(timeAgoTime) * 1000))));
- let utcMoment = moment.utc(new Date(parseInt(timeAgoTime) * 1000));
- let timeAgo = moment.duration(moment.utc(new Date()).diff(utcMoment));
- let future = false;
if (timeAgo.asSeconds() < 0)
@ -596,7 +661,7 @@ mixin timeAgo(timeAgoTime, options={oneElement:false, agoText:true})
if (hideHoverText)
span #{utils.summarizeDuration(timeAgo, options)}
else
span.border-dotted(title=utils.summarizeDuration(timeAgo), data-bs-toggle="tooltip") #{utils.summarizeDuration(timeAgo, options)}
span.border-dotted(title=`${utils.summarizeDuration(timeAgo)}<br>${utcMoment.format("Y-MM-DD HH:mm:ss")} utc`, data-bs-toggle="tooltip", data-bs-html="true") #{utils.summarizeDuration(timeAgo, options)}
else
span #{utils.summarizeDuration(timeAgo, options)}
@ -606,25 +671,35 @@ mixin timeAgo(timeAgoTime, options={oneElement:false, agoText:true})
mixin darkBadge(text)
span.badge.bg-dark.border.border-dark.me-2 #{text ? text : ""}
span.badge.text-bg-dark.border.border-dark.me-2 #{text ? text : ""}
if (block)
block
mixin lightBadge(text)
span.badge.bg-light.text-dark.border.me-2 #{text ? text : ""}
span.badge.text-bg-light.text-dark.border.me-2 #{text ? text : ""}
if (block)
block
mixin successBadge(text)
span.badge.bg-success.me-2 #{text ? text : ""}
span.badge.text-bg-success.me-2 #{text ? text : ""}
if (block)
block
mixin primaryBadge(text, endMargin=true)
span.badge.bg-primary(class=(endMargin ? "me-2" : false)) #{text ? text : ""}
span.badge.text-bg-primary(class=(endMargin ? "me-2" : false)) #{text ? text : ""}
if (block)
block
mixin secondaryBadge(text, endMargin=true)
span.badge.text-bg-secondary(class=(endMargin ? "me-2" : false)) #{text ? text : ""}
if (block)
block
mixin infoBadge(text, endMargin=true)
span.badge.text-bg-info(class=(endMargin ? "me-2" : false)) #{text ? text : ""}
if (block)
block

View file

@ -1,356 +0,0 @@
- var totalIOValues = utils.getTxTotalInputOutputValues(tx, txInputs, blockHeight);
mixin inputValueDisplay(tx, txVin, vout)
if (txVin.coinbase)
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.bi-asterisk.text-success
span(class=(tx.vin.length == 1 ? "fw-bold" : false))
- var currencyValue = coinConfig.blockRewardFunction(blockHeight, activeBlockchain);
+valueDisplay(currencyValue)
else
if (vout && vout.value)
span(class=(tx.vin.length == 1 ? "fw-bold" : "fw-light"))
+valueDisplay(vout.value)
else if (!global.txindexAvailable)
span(title="This data is unavailable. Your node must have its transaction index (txindex) setting enabled to view it.", data-bs-toggle="tooltip")
i.bi-backspace.text-danger
mixin outputValueDisplay(tx, vout, voutIndex)
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.bi-fire.text-danger.me-2
if (vout.value > 0)
span(class=(nonZeroVoutValueCount == 1 ? "fw-bold" : "fw-light"))
+valueDisplay(vout.value)
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.bi-check-circle.text-success.ms-2
else if (utxos[voutIndex] == null)
if (tx.txid == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
span(title="This output is UNSPENDABLE, and therefore these coins do not exist!" data-bs-toggle="tooltip")
i.bi-flag.text-warning.ms-2
else
span(title="This output has been spent (destroyed)." data-bs-toggle="tooltip")
i.bi-x-circle.text-danger.ms-2
.row
.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
if (!txVin.coinbase)
- var vout = null;
if (txInputs && txInputs[txVinIndex])
- var txInput = txInputs[txVinIndex];
- var vout = txInput;
else if (!global.txindexAvailable)
- var txInput = {txid:txVin.txid, vout:txVin.vout};
- var vout = {};
if (txVin.coinbase || vout || !global.txindexAvailable)
.clearfix
a.sz-xs-hidden.d-none.d-sm-inline.badge.fw-normal.card-highlight.border.text-decoration-none.me-2(data-bs-toggle="tooltip", title=`Input #${txVinIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-danger
span.d-none.d-sm-inline.font-monospace.ms-1 #{txVinIndex.toLocaleString()}
a.sz-xs-only.d-inline.d-sm-none.text-decoration-none.me-1(data-bs-toggle="tooltip", title=`Input #${txVinIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-danger
if (vout && vout.scriptPubKey && vout.scriptPubKey.type)
span.d-none.d-sm-inline(title=`Input Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip")
+darkBadge(utils.outputTypeAbbreviation(vout.scriptPubKey.type))
if (vout && vout.coinbaseSpend)
span.d-none.d-sm-inline
+successBadge
span(title=`This input spends a coinbase output (mining reward), meaning that this represents the first time these coins have been spent.`, data-bs-toggle="tooltip") 1<sup>st</sup> spend
span.ms-1.float-end
+inputValueDisplay(tx, txVin, vout)
if (txVin.coinbase)
+successBadge
span(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
.mt-2
+hexDataDisplay(txVin.coinbase)
else
span.word-wrap
if (vout.utxoTime)
- var deltaT = tx.time - vout.utxoTime
- var deltaTYears = deltaT / 60 / 60 / 24 / 365
if (deltaTYears > 5)
span.me-2.d-none.d-sm-inline(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")
+funItemIcon
span.mt-1
a.sz-xl-plus.d-none.d-xl-inline(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsizeMiddle(txInput.txid, 22)}
span.text-muted ##{txVin.vout}
a.sz-md-lg.d-none.d-md-inline.d-xl-none(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsizeMiddle(txInput.txid, 14)}
span.text-muted ##{txVin.vout}
a.sz-xs-sm.small.d-inline.d-md-none(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsizeMiddle(txInput.txid, 14)}
span.text-muted ##{txVin.vout}
if (vout && vout.scriptPubKey)
- var voutAddresses = utils.getVoutAddresses(vout);
if (voutAddresses.length > 0)
.word-wrap.mt-2.d-none.d-sm-block
a.sz-md-plus.d-none.d-md-inline(href=`./address/${voutAddresses[0]}`, class=(txIOHighlightAddress == voutAddresses[0] ? "text-info" : null)) #{utils.ellipsizeMiddle(voutAddresses[0], 44)}
a.sz-xs-sm.small.d-inline.d-md-none(href=`./address/${voutAddresses[0]}`, class=(txIOHighlightAddress == voutAddresses[0] ? "text-info" : null)) #{utils.ellipsizeMiddle(voutAddresses[0], 24)}
if (txIOHighlightAddress == voutAddresses[0])
span(title="Current address", data-bs-toggle="tooltip")
i.bi-arrow-left.text-warning.ms-1.text-info
if (global.specialAddresses[voutAddresses[0]])
- var specialAddressInfo = global.specialAddresses[voutAddresses[0]];
if (specialAddressInfo.type == "minerPayout")
span
a.d-none.d-sm-inline(data-bs-toggle="tooltip", title=`Miner payout address: ${specialAddressInfo.minerInfo.name}`)
+funItemIcon
.d-block.d-sm-none
div(class=`tx-input-address-${tx.txid}`, style="display: none;")
a.sz-xs-sm.small(href=`./address/${voutAddresses[0]}`, class=(txIOHighlightAddress == voutAddresses[0] ? "text-info" : null)) #{utils.ellipsizeMiddle(voutAddresses[0], 24)}
if (txIOHighlightAddress == voutAddresses[0])
span(title="Current address", data-bs-toggle="tooltip")
i.bi-arrow-left.text-warning.ms-1.text-info
if (tx.vin.length > 1 || txVinIndex > 0)
hr.mb-1
if (false)
small
span.border-dotted(title="Transaction output", data-bs-toggle="tooltip") txo
span.me-1 :
a(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsize(txInput.txid, 20)}[#{txVin.vout}]
else
if (tx.vin.length > 1)
hr.d-none.d-sm-block
else
- extraInputCount = extraInputCount + 1;
if (extraInputCount > 0)
.clearfix.mb-sm-2
a.sz-md-plus.d-none.d-sm-inline.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;")
i.bi-arrow-right.text-danger.me-sm-2
span.d-none.d-sm-inline …
a.sz-xs-only.d-inline.d-sm-none.text-decoration-none.me-1(data-bs-toggle="tooltip", title=`Input #${(tx.vin.length - extraInputCount + 1).toLocaleString()} - ${tx.vin.length.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-danger
small.text-muted.border-dotted.ms-0.ms-sm-2(data-bs-toggle="tooltip", title="The details for the remaining inputs were truncated for performance purposes. Click through to the transaction details to see all inputs.") (#{extraInputCount.toLocaleString()} truncated)
.float-end
- var currencyValue = new Decimal(totalIOValues.output).minus(new Decimal(totalIOValues.input));
span.border-dotted.me-1(title="The total amounts for other inputs are approximately equal to this value, plus the mining fee paid.", data-bs-toggle="tooltip") ~+
+valueDisplay(currencyValue)
hr.d-none.d-sm-block
if (totalIOValues.input && totalIOValues.input > 0 && tx.vin.length > 1)
.row.mb-0.mb-md-0
.col
.text-end.mt-1.mt-sm-n1
if (totalIOValues.input > 0 && extraInputCount == 0)
i.d-inline.d-lg-none.bi-arrow-right.text-danger.me-2
span.fw-bold
+valueDisplay(totalIOValues.input)
else if (extraInputCount > 0)
i.d-inline.d-lg-none.bi-arrow-right.text-danger.me-2
span.border-dotted.me-1(title="The total input amount for this transaction is approximately equal to this value, plus the mining fee paid.", data-bs-toggle="tooltip") ~+
span.fw-bold
+valueDisplay(totalIOValues.output)
.d-xs-block.d-sm-none
.my-3
hr
if (tx.vin.length == 1)
.d-none.d-sm-block.d-md-block.d-lg-none
.my-4
hr
else
.d-none.d-sm-block.d-lg-none
.mb-4
| &nbsp;
.col-lg-6(class=(tx.vout.length > tx.vin.length ? "border-lg-start" : false))
- var maxRegularRowCount = maxTxOutputDisplayCount || options.maxTxOutputDisplayCount || 15;
- var regularRowCount = 0;
- var hiddenRowCount = 0;
- var nonZeroVoutValueCount = tx.vout.filter(vout => { return vout.value > 0; }).length;
each vout, voutIndex in tx.vout
- var voutAddresses = utils.getVoutAddresses(vout);
- var highlightRow = false;
if (txIOHighlightAddress != null && voutAddresses.length > 0 && voutAddresses[0] == txIOHighlightAddress)
- highlightRow = true;
if (!highlightRow)
- regularRowCount++;
- var hiddenRow = false;
if (!highlightRow && regularRowCount > maxRegularRowCount)
- hiddenRow = true;
- hiddenRowCount++;
div(data-txid=tx.txid, class=(hiddenRow ? "d-none" : ""))
.clearfix.mb-0.mb-sm-2.mb-md-3
a.xs-hidden.d-none.d-sm-inline.badge.card-highlight.border.text-decoration-none.fw-normal.me-2(data-bs-toggle="tooltip", title=`Output #${voutIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-success
span.d-none.d-sm-inline.font-monospace.ms-1 #{voutIndex.toLocaleString()}
a.xs-only.d-inline.d-sm-none.text-decoration-none.fw-normal.me-1(data-bs-toggle="tooltip", title=`Output #${voutIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-success
if (vout && vout.scriptPubKey)
if (vout.scriptPubKey.hex && vout.scriptPubKey.hex.startsWith('6a24aa21a9ed'))
span
+primaryBadge("OP_RETURN")
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")
+darkBadge
span.small SegWit
i.bi-box-arrow-up-right.ms-1
.my-2
+hexDataDisplay(vout.scriptPubKey.asm.substring("OP_RETURN ".length))
else if (vout.scriptPubKey.asm && vout.scriptPubKey.asm.startsWith('OP_RETURN '))
span
+primaryBadge("OP_RETURN")
.my-2
+hexDataDisplay(vout.scriptPubKey.asm.substring("OP_RETURN ".length))
else if (vout.scriptPubKey.type)
span.d-none.d-sm-inline
+darkBadge
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}
if (voutAddresses.length == 0)
// xs display only, output type when we have no address (e.g. op_return)
if (vout.scriptPubKey.asm && vout.scriptPubKey.asm.startsWith('OP_RETURN '))
if (false)
span.small.xs-only.d-inline.d-sm-none
+primaryBadge("OP_RETURN")
.xs-sm.d-inline-block.d-md-none.small.border.rounded-1.px-1.card-highlight.mt-1
- var asm = vout.scriptPubKey.asm.substring("OP_RETURN ".length);
span.text-tiny.fw-light #{utils.formatHex(asm, "utf8")}
else if (vout.scriptPubKey.type)
span.small.xs-only.d-inline.d-sm-none
+darkBadge
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}
span.ms-2.float-end
+outputValueDisplay(tx, vout, voutIndex)
if (vout && vout.scriptPubKey.asm && !vout.scriptPubKey.asm.startsWith("OP_RETURN "))
if (voutAddresses.length == 0)
.my-2
+hexDataDisplay(vout.scriptPubKey.hex)
if (voutAddresses.length > 0)
.d-inline.d-lg-block.mt-lg-2
each addr in voutAddresses
a.sz-lg-plus.d-none.d-lg-inline(id=`output-${voutIndex}`, href=`./address/${addr}`)
span(class=(highlightRow ? "text-info" : null)) #{utils.ellipsizeMiddle(addr, 44)}
a.sz-md.d-none.d-md-inline.d-lg-none(id=`output-${voutIndex}`, href=`./address/${addr}`)
span(class=(highlightRow ? "text-info" : null)) #{utils.ellipsizeMiddle(addr, 24)}
a.sz-xs-sm.d-inline.d-md-none(id=`output-${voutIndex}`, href=`./address/${addr}`)
span.small(class=(highlightRow ? "text-info" : null)) #{utils.ellipsizeMiddle(addr, 18)}
if (highlightRow)
span(title="Current address", data-bs-toggle="tooltip")
i.bi-arrow-left.ms-1.text-warning
if (global.specialAddresses[addr])
- var specialAddressInfo = global.specialAddresses[addr];
if (specialAddressInfo.type == "minerPayout")
span
a.d-none.d-sm-inline(data-bs-toggle="tooltip", title=`Miner payout address: ${specialAddressInfo.minerInfo.name}`)
+funItemIcon
.d-none.d-sm-block.mb-2
if (vout.scriptPubKey)
if (voutAddresses.length > 0)
else if (false)
.mb-tiny
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip")
+darkBadge(utils.outputTypeAbbreviation(vout.scriptPubKey.type))
+hexDataDisplay(vout.scriptPubKey.asm)
if (voutIndex < (tx.vout.length - 1) || nonZeroVoutValueCount > 1)
hr.d-none.d-md-block
else
.mb-0
if (hiddenRowCount > 0)
a.small(href="javascript:void(0)", onclick=("showAllTxOutputs(this, '" + tx.txid + "');"))
i.bi-plus-square.text-card-highlight.me-1
| Show remaining #{hiddenRowCount.toLocaleString()} outputs
if (nonZeroVoutValueCount > 1)
.row.mb-0.mb-lg-0
.col
.text-end.mt-1.mt-sm-n1(style=(utxos ? "margin-right: 1.39rem !important;" : false))
span.fw-bold
i.d-inline.d-lg-none.bi-arrow-right.text-success.me-2
+valueDisplay(totalIOValues.output)

View file

@ -0,0 +1,362 @@
mixin inputValueDisplay(tx, txVin, vout)
if (txVin.coinbase)
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.bi-asterisk.text-success
span(class=(tx.vin.length == 1 ? "fw-bold" : false))
- var currencyValue = coinConfig.blockRewardFunction(blockHeight, activeBlockchain);
+valueDisplay(currencyValue)
else
if (vout && vout.value)
span(class=(tx.vin.length == 1 ? "fw-bold" : "fw-light"))
+valueDisplay(vout.value)
else if (!global.txindexAvailable)
span(title="This data is unavailable. Your node must have its transaction index (txindex) setting enabled to view it.", data-bs-toggle="tooltip")
i.bi-backspace.text-danger
mixin outputValueDisplay(tx, vout, voutIndex, nonZeroVoutValueCount)
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.bi-fire.text-danger.me-2
if (vout.value > 0)
span(class=(nonZeroVoutValueCount == 1 ? "fw-bold" : "fw-light"))
+valueDisplay(vout.value)
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.bi-check-circle.text-success.ms-2
else if (utxos[voutIndex] == null)
if (tx.txid == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
span(title="This output is UNSPENDABLE, and therefore these coins do not exist!" data-bs-toggle="tooltip")
i.bi-flag.text-warning.ms-2
else
span(title="This output has been spent (destroyed)." data-bs-toggle="tooltip")
i.bi-x-circle.text-danger.ms-2
mixin txIoDetails(tx, txInputs, totalIOValues, blockHeight, options={})
if (options.highlightAddress)
- txIOHighlightAddress = options.highlightAddress
if (options.maxTxOutputDisplayCount)
- maxTxOutputDisplayCount = options.maxTxOutputDisplayCount
.row
.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
if (!txVin.coinbase)
- var vout = null;
if (txInputs && txInputs[txVinIndex])
- var txInput = txInputs[txVinIndex];
- var vout = txInput;
else if (!global.txindexAvailable)
- var txInput = {txid:txVin.txid, vout:txVin.vout};
- var vout = {};
if (txVin.coinbase || vout || !global.txindexAvailable)
.clearfix
a.sz-xs-hidden.d-none.d-sm-inline.badge.fw-normal.card-highlight.border.text-decoration-none.me-2(data-bs-toggle="tooltip", title=`Input #${txVinIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-danger
span.d-none.d-sm-inline.font-monospace.ms-1 #{txVinIndex.toLocaleString()}
a.sz-xs-only.d-inline.d-sm-none.text-decoration-none.me-1(data-bs-toggle="tooltip", title=`Input #${txVinIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-danger
if (vout && vout.scriptPubKey && vout.scriptPubKey.type)
span.d-none.d-sm-inline(title=`Input Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip")
+darkBadge(utils.outputTypeAbbreviation(vout.scriptPubKey.type))
if (vout && vout.coinbaseSpend)
span.d-none.d-sm-inline
+successBadge
span(title=`This input spends a coinbase output (mining reward), meaning that this represents the first time these coins have been spent.`, data-bs-toggle="tooltip") 1<sup>st</sup> spend
span.ms-1.float-end
+inputValueDisplay(tx, txVin, vout)
if (txVin.coinbase)
+successBadge
span(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
.mt-2
+hexDataDisplay(txVin.coinbase)
else
span.word-wrap
if (vout.utxoTime)
- var deltaT = tx.time - vout.utxoTime
- var deltaTYears = deltaT / 60 / 60 / 24 / 365
if (deltaTYears > 5)
span.me-2.d-none.d-sm-inline(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")
+funItemIcon
span.mt-1
a.sz-xl-plus.d-none.d-xl-inline(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsizeMiddle(txInput.txid, 22)}
span.text-muted ##{txVin.vout}
a.sz-md-lg.d-none.d-md-inline.d-xl-none(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsizeMiddle(txInput.txid, 14)}
span.text-muted ##{txVin.vout}
a.sz-xs-sm.small.d-inline.d-md-none(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsizeMiddle(txInput.txid, 14)}
span.text-muted ##{txVin.vout}
if (vout && vout.scriptPubKey)
- var voutAddresses = utils.getVoutAddresses(vout);
if (voutAddresses.length > 0)
.word-wrap.mt-2.d-none.d-sm-block
a.sz-md-plus.d-none.d-md-inline(href=`./address/${voutAddresses[0]}`, class=(txIOHighlightAddress == voutAddresses[0] ? "text-info" : null)) #{utils.ellipsizeMiddle(voutAddresses[0], 44)}
a.sz-xs-sm.small.d-inline.d-md-none(href=`./address/${voutAddresses[0]}`, class=(txIOHighlightAddress == voutAddresses[0] ? "text-info" : null)) #{utils.ellipsizeMiddle(voutAddresses[0], 24)}
if (txIOHighlightAddress == voutAddresses[0])
span(title="Current address", data-bs-toggle="tooltip")
i.bi-arrow-left.text-warning.ms-1.text-info
if (global.specialAddresses[voutAddresses[0]])
- var specialAddressInfo = global.specialAddresses[voutAddresses[0]];
if (specialAddressInfo.type == "minerPayout")
span
a.d-none.d-sm-inline(data-bs-toggle="tooltip", title=`Miner payout address: ${specialAddressInfo.minerInfo.name}`)
+funItemIcon
.d-block.d-sm-none
div(class=`tx-input-address-${tx.txid}`, style="display: none;")
a.sz-xs-sm.small(href=`./address/${voutAddresses[0]}`, class=(txIOHighlightAddress == voutAddresses[0] ? "text-info" : null)) #{utils.ellipsizeMiddle(voutAddresses[0], 24)}
if (txIOHighlightAddress == voutAddresses[0])
span(title="Current address", data-bs-toggle="tooltip")
i.bi-arrow-left.text-warning.ms-1.text-info
if (tx.vin.length > 1 || txVinIndex > 0)
hr.mb-1
if (false)
small
span.border-dotted(title="Transaction output", data-bs-toggle="tooltip") txo
span.me-1 :
a(href=`./tx/${txInput.txid}#output-${txVin.vout}`) #{utils.ellipsize(txInput.txid, 20)}[#{txVin.vout}]
else
if (tx.vin.length > 1)
hr.d-none.d-sm-block
else
- extraInputCount = extraInputCount + 1;
if (extraInputCount > 0)
.clearfix.mb-sm-2
a.sz-md-plus.d-none.d-sm-inline.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;")
i.bi-arrow-right.text-danger.me-sm-2
span.d-none.d-sm-inline …
a.sz-xs-only.d-inline.d-sm-none.text-decoration-none.me-1(data-bs-toggle="tooltip", title=`Input #${(tx.vin.length - extraInputCount + 1).toLocaleString()} - ${tx.vin.length.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-danger
small.text-muted.border-dotted.ms-0.ms-sm-2(data-bs-toggle="tooltip", title="The details for the remaining inputs were truncated for performance purposes. Click through to the transaction details to see all inputs.") (#{extraInputCount.toLocaleString()} truncated)
.float-end
- var currencyValue = new Decimal(totalIOValues.output).minus(new Decimal(totalIOValues.input));
span.border-dotted.me-1(title="The total amounts for other inputs are approximately equal to this value, plus the mining fee paid.", data-bs-toggle="tooltip") ~+
+valueDisplay(currencyValue)
hr.d-none.d-sm-block
if (totalIOValues.input && totalIOValues.input > 0 && tx.vin.length > 1)
.row.mb-0.mb-md-0
.col
.text-end.mt-1.mt-sm-n1
if (totalIOValues.input > 0 && extraInputCount == 0)
i.d-inline.d-lg-none.bi-arrow-right.text-danger.me-2
span.fw-bold
+valueDisplay(totalIOValues.input)
else if (extraInputCount > 0)
i.d-inline.d-lg-none.bi-arrow-right.text-danger.me-2
span.border-dotted.me-1(title="The total input amount for this transaction is approximately equal to this value, plus the mining fee paid.", data-bs-toggle="tooltip") ~+
span.fw-bold
+valueDisplay(totalIOValues.output)
.d-xs-block.d-sm-none
.my-3
hr
if (tx.vin.length == 1)
.d-none.d-sm-block.d-md-block.d-lg-none
.my-4
hr
else
.d-none.d-sm-block.d-lg-none
.mb-4
| &nbsp;
.col-lg-6(class=(tx.vout.length > tx.vin.length ? "border-lg-start" : false))
- var maxRegularRowCount = maxTxOutputDisplayCount || options.maxTxOutputDisplayCount || 15;
- var regularRowCount = 0;
- var hiddenRowCount = 0;
- var nonZeroVoutValueCount = tx.vout.filter(vout => { return vout.value > 0; }).length;
each vout, voutIndex in tx.vout
- var voutAddresses = utils.getVoutAddresses(vout);
- var highlightRow = false;
if (txIOHighlightAddress != null && voutAddresses.length > 0 && voutAddresses[0] == txIOHighlightAddress)
- highlightRow = true;
if (!highlightRow)
- regularRowCount++;
- var hiddenRow = false;
if (!highlightRow && regularRowCount > maxRegularRowCount)
- hiddenRow = true;
- hiddenRowCount++;
div(data-txid=tx.txid, class=(hiddenRow ? "d-none" : ""))
.clearfix.mb-0.mb-sm-2.mb-md-3
a.xs-hidden.d-none.d-sm-inline.badge.card-highlight.border.text-decoration-none.fw-normal.me-2(data-bs-toggle="tooltip", title=`Output #${voutIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-success
span.d-none.d-sm-inline.font-monospace.ms-1 #{voutIndex.toLocaleString()}
a.xs-only.d-inline.d-sm-none.text-decoration-none.fw-normal.me-1(data-bs-toggle="tooltip", title=`Output #${voutIndex.toLocaleString()}`, style="white-space: nowrap;")
i.d-inline.bi-arrow-right.text-success
if (vout && vout.scriptPubKey)
if (vout.scriptPubKey.hex && vout.scriptPubKey.hex.startsWith('6a24aa21a9ed'))
span
+primaryBadge("OP_RETURN")
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")
+darkBadge
span.small SegWit
i.bi-box-arrow-up-right.ms-1
.my-2
+hexDataDisplay(vout.scriptPubKey.asm.substring("OP_RETURN ".length))
else if (vout.scriptPubKey.asm && vout.scriptPubKey.asm.startsWith('OP_RETURN '))
span
+primaryBadge("OP_RETURN")
.my-2
+hexDataDisplay(vout.scriptPubKey.asm.substring("OP_RETURN ".length))
else if (vout.scriptPubKey.type)
span.d-none.d-sm-inline
+darkBadge
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}
if (voutAddresses.length == 0)
// xs display only, output type when we have no address (e.g. op_return)
if (vout.scriptPubKey.asm && vout.scriptPubKey.asm.startsWith('OP_RETURN '))
if (false)
span.small.xs-only.d-inline.d-sm-none
+primaryBadge("OP_RETURN")
.xs-sm.d-inline-block.d-md-none.small.border.rounded-1.px-1.card-highlight.mt-1
- var asm = vout.scriptPubKey.asm.substring("OP_RETURN ".length);
span.text-tiny.fw-light #{utils.formatHex(asm, "utf8")}
else if (vout.scriptPubKey.type)
span.small.xs-only.d-inline.d-sm-none
+darkBadge
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip") #{utils.outputTypeAbbreviation(vout.scriptPubKey.type)}
span.ms-2.float-end
+outputValueDisplay(tx, vout, voutIndex, nonZeroVoutValueCount)
if (vout && vout.scriptPubKey.asm && !vout.scriptPubKey.asm.startsWith("OP_RETURN "))
if (voutAddresses.length == 0)
.my-2
+hexDataDisplay(vout.scriptPubKey.hex)
if (voutAddresses.length > 0)
.d-inline.d-lg-block.mt-lg-2
each addr in voutAddresses
a.sz-lg-plus.d-none.d-lg-inline(id=`output-${voutIndex}`, href=`./address/${addr}`)
span(class=(highlightRow ? "text-info" : null)) #{utils.ellipsizeMiddle(addr, 44)}
a.sz-md.d-none.d-md-inline.d-lg-none(id=`output-${voutIndex}`, href=`./address/${addr}`)
span(class=(highlightRow ? "text-info" : null)) #{utils.ellipsizeMiddle(addr, 24)}
a.sz-xs-sm.d-inline.d-md-none(id=`output-${voutIndex}`, href=`./address/${addr}`)
span.small(class=(highlightRow ? "text-info" : null)) #{utils.ellipsizeMiddle(addr, 18)}
if (highlightRow)
span(title="Current address", data-bs-toggle="tooltip")
i.bi-arrow-left.ms-1.text-warning
if (global.specialAddresses[addr])
- var specialAddressInfo = global.specialAddresses[addr];
if (specialAddressInfo.type == "minerPayout")
span
a.d-none.d-sm-inline(data-bs-toggle="tooltip", title=`Miner payout address: ${specialAddressInfo.minerInfo.name}`)
+funItemIcon
.d-none.d-sm-block.mb-2
if (vout.scriptPubKey)
if (voutAddresses.length > 0)
else if (false)
.mb-tiny
span(title=`Output Type: ${utils.outputTypeName(vout.scriptPubKey.type)}`, data-bs-toggle="tooltip")
+darkBadge(utils.outputTypeAbbreviation(vout.scriptPubKey.type))
+hexDataDisplay(vout.scriptPubKey.asm)
if (voutIndex < (tx.vout.length - 1) || nonZeroVoutValueCount > 1)
hr.d-none.d-md-block
else
.mb-0
if (hiddenRowCount > 0)
a.small(href="javascript:void(0)", onclick=("showAllTxOutputs(this, '" + tx.txid + "');"))
i.bi-plus-square.text-card-highlight.me-1
| Show remaining #{hiddenRowCount.toLocaleString()} outputs
if (nonZeroVoutValueCount > 1)
.row.mb-0.mb-lg-0
.col
.text-end.mt-1.mt-sm-n1(style=(utxos ? "margin-right: 1.39rem !important;" : false))
span.fw-bold
i.d-inline.d-lg-none.bi-arrow-right.text-success.me-2
+valueDisplay(totalIOValues.output)

View file

@ -1,4 +1,5 @@
include ./includes/shared-mixins.pug
include ./includes/tx-mixins.pug
doctype html
html(lang="en")

View file

@ -273,7 +273,9 @@ block content
if (result && result.getblock)
- blockHeight = result.getblock.height;
include includes/transaction-io-details.pug
- var totalIOValues = utils.getTxTotalInputOutputValues(tx, txInputs, blockHeight);
+txIoDetails(tx, txInputs, totalIOValues, blockHeight)
if (mempoolDetails)