mirror of
https://github.com/mempool/mempool.git
synced 2025-01-17 18:52:34 +01:00
Display Segwit and RBF information
This commit is contained in:
parent
84e15b133a
commit
3deedada07
68
frontend/src/app/bitcoin.utils.ts
Normal file
68
frontend/src/app/bitcoin.utils.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Transaction, Vin } from './interfaces/electrs.interface';
|
||||||
|
|
||||||
|
const P2SH_P2WPKH_COST = 21 * 4; // the WU cost for the non-witness part of P2SH-P2WPKH
|
||||||
|
const P2SH_P2WSH_COST = 35 * 4; // the WU cost for the non-witness part of P2SH-P2WSH
|
||||||
|
|
||||||
|
export function calcSegwitFeeGains(tx: Transaction) {
|
||||||
|
// calculated in weight units
|
||||||
|
let realizedGains = 0;
|
||||||
|
let potentialBech32Gains = 0;
|
||||||
|
let potentialP2shGains = 0;
|
||||||
|
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
if (!vin.prevout) { continue; }
|
||||||
|
|
||||||
|
const isP2pkh = vin.prevout.scriptpubkey_type === 'p2pkh';
|
||||||
|
const isP2sh = vin.prevout.scriptpubkey_type === 'p2sh';
|
||||||
|
const isP2wsh = vin.prevout.scriptpubkey_type === 'v0_p2wsh';
|
||||||
|
const isP2wpkh = vin.prevout.scriptpubkey_type === 'v0_p2wpkh';
|
||||||
|
|
||||||
|
const op = vin.scriptsig ? vin.scriptsig_asm.split(' ')[0] : null;
|
||||||
|
const isP2sh2Wpkh = isP2sh && !!vin.witness && op === 'OP_PUSHBYTES_22';
|
||||||
|
const isP2sh2Wsh = isP2sh && !!vin.witness && op === 'OP_PUSHBYTES_34';
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
// Native Segwit - P2WPKH/P2WSH (Bech32)
|
||||||
|
case isP2wpkh:
|
||||||
|
case isP2wsh:
|
||||||
|
// maximal gains: the scriptSig is moved entirely to the witness part
|
||||||
|
realizedGains += witnessSize(vin) * 3;
|
||||||
|
// XXX P2WSH output creation is more expensive, should we take this into consideration?
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Backward compatible Segwit - P2SH-P2WPKH
|
||||||
|
case isP2sh2Wpkh:
|
||||||
|
// the scriptSig is moved to the witness, but we have extra 21 extra non-witness bytes (48 WU)
|
||||||
|
realizedGains += witnessSize(vin) * 3 - P2SH_P2WPKH_COST;
|
||||||
|
potentialBech32Gains += P2SH_P2WPKH_COST;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Backward compatible Segwit - P2SH-P2WSH
|
||||||
|
case isP2sh2Wsh:
|
||||||
|
// the scriptSig is moved to the witness, but we have extra 35 extra non-witness bytes
|
||||||
|
realizedGains += witnessSize(vin) * 3 - P2SH_P2WSH_COST;
|
||||||
|
potentialBech32Gains += P2SH_P2WSH_COST;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Non-segwit P2PKH/P2SH
|
||||||
|
case isP2pkh:
|
||||||
|
case isP2sh:
|
||||||
|
const fullGains = scriptSigSize(vin) * 3;
|
||||||
|
potentialBech32Gains += fullGains;
|
||||||
|
potentialP2shGains += fullGains - (isP2pkh ? P2SH_P2WPKH_COST : P2SH_P2WSH_COST);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO: should we also consider P2PK and pay-to-bare-script (non-p2sh-wrapped) as upgradable to P2WPKH and P2WSH?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returned as percentage of the total tx weight
|
||||||
|
return { realizedGains: realizedGains / (tx.weight + realizedGains) // percent of the pre-segwit tx size
|
||||||
|
, potentialBech32Gains: potentialBech32Gains / tx.weight
|
||||||
|
, potentialP2shGains: potentialP2shGains / tx.weight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utilities for segwitFeeGains
|
||||||
|
const witnessSize = (vin: Vin) => vin.witness.reduce((S, w) => S + (w.length / 2), 0);
|
||||||
|
const scriptSigSize = (vin: Vin) => vin.scriptsig ? vin.scriptsig.length / 2 : 0;
|
@ -43,6 +43,7 @@
|
|||||||
<td>After <app-timespan [time]="tx.status.block_time - transactionTime"></app-timespan></td>
|
<td>After <app-timespan [time]="tx.status.block_time - transactionTime"></app-timespan></td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
<ng-container *ngTemplateOutlet="features"></ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -115,6 +116,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<ng-container *ngTemplateOutlet="features"></ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -250,3 +252,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
<ng-template #features>
|
||||||
|
<tr *ngIf="network !== 'liquid'">
|
||||||
|
<td class="td-width">Features</td>
|
||||||
|
<td>
|
||||||
|
<span *ngIf="segwitGains.realizedGains && !segwitGains.potentialBech32Gains" class="badge badge-success mr-1" ngbTooltip="This transaction saved {{ segwitGains.realizedGains * 100 | number: '1.0-0' }}% on fees by using native SegWit-Bech32" placement="bottom">SegWit</span>
|
||||||
|
<span *ngIf="segwitGains.realizedGains && segwitGains.potentialBech32Gains" class="badge badge-warning mr-1" ngbTooltip="This transaction saved {{ segwitGains.realizedGains * 100 | number: '1.0-0' }}% on fees by using SegWit and could save {{ segwitGains.potentialBech32Gains * 100 | number : '1.0-0' }}% more by fully upgrading to native SegWit-Bech32" placement="bottom">SegWit</span>
|
||||||
|
<span *ngIf="segwitGains.potentialP2shGains" class="badge badge-danger mr-1" ngbTooltip="This transaction could save {{ segwitGains.potentialBech32Gains * 100 | number : '1.0-0' }}% on fees by upgrading to native SegWit-Bech32 or {{ segwitGains.potentialP2shGains * 100 | number: '1.0-0' }}% by upgrading to SegWit-P2SH" placement="bottom"><del>SegWit</del></span>
|
||||||
|
<span *ngIf="isRbfTransaction" class="badge badge-success" ngbTooltip="This transaction support Replace-By-Fee (RBF) allowing fee bumping" placement="bottom">RBF</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
@ -9,6 +9,7 @@ import { WebsocketService } from '../../services/websocket.service';
|
|||||||
import { AudioService } from 'src/app/services/audio.service';
|
import { AudioService } from 'src/app/services/audio.service';
|
||||||
import { ApiService } from 'src/app/services/api.service';
|
import { ApiService } from 'src/app/services/api.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
|
import { calcSegwitFeeGains } from 'src/app/bitcoin.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transaction',
|
selector: 'app-transaction',
|
||||||
@ -29,6 +30,12 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
latestBlock: Block;
|
latestBlock: Block;
|
||||||
transactionTime = -1;
|
transactionTime = -1;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
|
segwitGains = {
|
||||||
|
realizedGains: 0,
|
||||||
|
potentialBech32Gains: 0,
|
||||||
|
potentialP2shGains: 0,
|
||||||
|
};
|
||||||
|
isRbfTransaction: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -79,6 +86,8 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
this.error = undefined;
|
this.error = undefined;
|
||||||
this.waitingForTransaction = false;
|
this.waitingForTransaction = false;
|
||||||
this.setMempoolBlocksSubscription();
|
this.setMempoolBlocksSubscription();
|
||||||
|
this.segwitGains = calcSegwitFeeGains(tx);
|
||||||
|
this.isRbfTransaction = tx.vin.some((v) => v.sequence < 0xfffffffe);
|
||||||
|
|
||||||
if (!tx.status.confirmed) {
|
if (!tx.status.confirmed) {
|
||||||
this.websocketService.startTrackTransaction(tx.txid);
|
this.websocketService.startTrackTransaction(tx.txid);
|
||||||
|
@ -9,6 +9,7 @@ $nav-tabs-link-active-bg: #11131f;
|
|||||||
|
|
||||||
$primary: #105fb0;
|
$primary: #105fb0;
|
||||||
$secondary: #2d3348;
|
$secondary: #2d3348;
|
||||||
|
$success: #1a9436;
|
||||||
|
|
||||||
$link-color: #1bd8f4;
|
$link-color: #1bd8f4;
|
||||||
$link-decoration: none !default;
|
$link-decoration: none !default;
|
||||||
|
Loading…
Reference in New Issue
Block a user