mirror of
https://github.com/mempool/mempool.git
synced 2025-09-26 18:16:43 +02:00
Support for sighash display preferences
This commit is contained in:
@@ -147,7 +147,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<app-transactions-list #txList [transactions]="[transaction]" [transactionPage]="true" [txPreview]="true" [signatures]="true"></app-transactions-list>
|
||||
<app-transactions-list #txList [transactions]="[transaction]" [transactionPage]="true" [txPreview]="true" forceSignaturesMode="all"></app-transactions-list>
|
||||
|
||||
<div class="title text-left">
|
||||
<h2 i18n="transaction.details|Transaction Details">Details</h2>
|
||||
|
@@ -55,21 +55,21 @@
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</td>
|
||||
@if (signatures) {
|
||||
@if (tx['_showSignatures']) {
|
||||
<td class="sig-td">
|
||||
<div class="sig-stack">
|
||||
@if (tx['_sigs'][vindex].length === 0) {
|
||||
<span class="sig sig-key sig-no-lock" title="unsigned">
|
||||
@if (tx['_sigs'][vindex].length === 0 && signaturesMode === 'all') {
|
||||
<span class="sig sig-key sig-no-lock" ngbTooltip="unsigned">
|
||||
<fa-icon [icon]="['fas', 'lock-open']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
} @else {
|
||||
} @else if (showSig(tx['_sigs'][vindex])) {
|
||||
@for (sig of tx['_sigs'][vindex].slice(0, 7); track sig.signature; let idx = $index) {
|
||||
@if (idx < 7) {
|
||||
<span
|
||||
class="sig sig-key sighash-{{sig.sighash}}"
|
||||
(mouseenter)="showSigInfo(i, vindex, sig)"
|
||||
(mouseleave)="hideSigInfo()"
|
||||
[title]="sighashLabels[sig.sighash]"
|
||||
[ngbTooltip]="sighashLabels[sig.sighash]"
|
||||
[class.hovered]="selectedSig && selectedSig.txIndex === i && selectedSig.vindex === vindex && selectedSig.sig === sig"
|
||||
>
|
||||
<fa-icon [icon]="['fas', 'key']" [fixedWidth]="true"></fa-icon>
|
||||
@@ -77,7 +77,7 @@
|
||||
}
|
||||
}
|
||||
@if (tx['_sigs'][vindex].length > 7) {
|
||||
<span class="sig sig-key sig-overflow">
|
||||
<span class="sig sig-overflow">
|
||||
+{{ tx['_sigs'][vindex].length - 7 }}
|
||||
</span>
|
||||
}
|
||||
@@ -141,7 +141,7 @@
|
||||
'sigged': selectedSig && selectedSig.txIndex === i && sigHighlights.vin[vindex],
|
||||
}">
|
||||
<td></td>
|
||||
@if (signatures) {
|
||||
@if (tx['_showSignatures']) {
|
||||
<td></td>
|
||||
}
|
||||
<td colspan="2">
|
||||
@@ -149,7 +149,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="(showDetails$ | async) === true">
|
||||
<td [attr.colspan]="signatures ? 4 : 3" class="details-container" >
|
||||
<td [attr.colspan]="tx['_showSignatures'] ? 4 : 3" class="details-container" >
|
||||
<table class="table table-striped table-fixed table-borderless details-table mb-3">
|
||||
<tbody>
|
||||
<ng-template [ngIf]="vin.scriptsig">
|
||||
@@ -174,12 +174,12 @@
|
||||
<td style="text-align: left;">
|
||||
<ng-container *ngFor="let witness of vin.witness; index as windex">
|
||||
<p class="witness-item">
|
||||
<ng-container *ngIf="signatures && tx['_sigmap'][witness] as sigInfo">
|
||||
<ng-container *ngIf="tx['_sigmap'][witness] as sigInfo">
|
||||
<span class="sig sig-key sig-inline sighash-{{sigInfo.sig.sighash}}"
|
||||
[class.hovered]="selectedSig && selectedSig.txIndex === i && selectedSig.vindex === vindex && selectedSig.sig === sigInfo.sig"
|
||||
(mouseenter)="showSigInfo(i, vindex, sigInfo.sig)"
|
||||
(mouseleave)="hideSigInfo()"
|
||||
[title]="sighashLabels[sigInfo.sig.sighash]">
|
||||
[ngbTooltip]="sighashLabels[sigInfo.sig.sighash]">
|
||||
<fa-icon [icon]="['fas', 'key']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</ng-container>
|
||||
@@ -190,13 +190,13 @@
|
||||
{{ witness }}
|
||||
}
|
||||
} @else if (witness) {
|
||||
<ng-container *ngIf="signatures && tx['_sigmap'][witness]?.sig.sighash !== 0 && tx['_sigmap'][witness] as sigInfo; else plainSig">
|
||||
<ng-container *ngIf="tx['_sigmap'][witness]?.sig.sighash !== 0 && tx['_sigmap'][witness] as sigInfo; else plainSig">
|
||||
<span class="witness">
|
||||
{{witness.slice(0, -2)}}<span class="sig sighash-{{sigInfo.sig.sighash}}"
|
||||
[class.hovered]="selectedSig && selectedSig.txIndex === i && selectedSig.vindex === vindex && selectedSig.sig === sigInfo.sig"
|
||||
(mouseenter)="showSigInfo(i, vindex, sigInfo.sig)"
|
||||
(mouseleave)="hideSigInfo()"
|
||||
[title]="sighashLabels[sigInfo.sig.sighash]">{{witness.slice(-2)}}</span>
|
||||
[ngbTooltip]="sighashLabels[sigInfo.sig.sighash]">{{witness.slice(-2)}}</span>
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-template #plainSig>
|
||||
@@ -263,7 +263,7 @@
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="tx.vin.length > getVinLimit(tx)">
|
||||
<td [attr.colspan]="signatures ? 4 : 3" class="text-center">
|
||||
<td [attr.colspan]="tx['_showSignatures'] ? 4 : 3" class="text-center">
|
||||
<button class="btn btn-sm btn-primary mt-2" (click)="showMoreInputs(tx)">
|
||||
<span *ngIf="getVinLimit(tx, true) >= tx.vin.length; else showMoreInputsLabel" i18n="show-all">Show all</span>
|
||||
<ng-template #showMoreInputsLabel>
|
||||
@@ -374,7 +374,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="(showDetails$ | async) === true">
|
||||
<td [attr.colspan]="signatures ? 4 : 3" class=" details-container" >
|
||||
<td [attr.colspan]="tx['_showSignatures'] ? 4 : 3" class=" details-container" >
|
||||
<table class="table table-striped table-borderless details-table mb-3">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||
import { StateService, SignaturesMode } from '@app/services/state.service';
|
||||
import { CacheService } from '@app/services/cache.service';
|
||||
import { Observable, ReplaySubject, BehaviorSubject, merge, Subscription, of, forkJoin } from 'rxjs';
|
||||
import { Outspend, Transaction, Vin, Vout } from '@interfaces/electrs.interface';
|
||||
@@ -16,6 +16,8 @@ import { Inscription } from '@app/shared/ord/inscription.utils';
|
||||
import { Etching, Runestone } from '@app/shared/ord/rune.utils';
|
||||
import { ADDRESS_SIMILARITY_THRESHOLD, AddressMatch, AddressSimilarity, AddressType, AddressTypeInfo, checkedCompareAddressStrings, detectAddressType } from '@app/shared/address-utils';
|
||||
import { processInputSignatures, Sighash, SigInfo, SighashLabels } from '@app/shared/transaction.utils';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SighashFlag } from '../../shared/transaction.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transactions-list',
|
||||
@@ -23,7 +25,7 @@ import { processInputSignatures, Sighash, SigInfo, SighashLabels } from '@app/sh
|
||||
styleUrls: ['./transactions-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy {
|
||||
network = '';
|
||||
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
|
||||
showMoreIncrement = 1000;
|
||||
@@ -40,13 +42,16 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
@Input() rowLimit = 12;
|
||||
@Input() blockTime: number = 0; // Used for price calculation if all the transactions are in the same block
|
||||
@Input() txPreview = false;
|
||||
@Input() signatures = true;
|
||||
@Input() forceSignaturesMode: SignaturesMode = null;
|
||||
|
||||
@Output() loadMore = new EventEmitter();
|
||||
|
||||
latestBlock$: Observable<BlockExtended>;
|
||||
outspendsSubscription: Subscription;
|
||||
currencyChangeSubscription: Subscription;
|
||||
networkSubscription: Subscription;
|
||||
signaturesSubscription: Subscription;
|
||||
queryParamsSubscription: Subscription;
|
||||
currency: string;
|
||||
refreshOutspends$: ReplaySubject<string[]> = new ReplaySubject();
|
||||
refreshChannels$: ReplaySubject<string[]> = new ReplaySubject();
|
||||
@@ -64,6 +69,10 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
sigHighlights: { vin: boolean[], vout: boolean[] } = { vin: [], vout: [] };
|
||||
sighashLabels = SighashLabels;
|
||||
|
||||
signaturesPreference: SignaturesMode = null;
|
||||
signaturesOverride: SignaturesMode = null;
|
||||
signaturesMode: SignaturesMode = 'interesting';
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
@@ -74,11 +83,29 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
private ref: ChangeDetectorRef,
|
||||
private priceService: PriceService,
|
||||
private storageService: StorageService,
|
||||
) { }
|
||||
private route: ActivatedRoute,
|
||||
) {
|
||||
this.signaturesMode = this.forceSignaturesMode || this.stateService.signaturesMode$.value;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.latestBlock$ = this.stateService.blocks$.pipe(map((blocks) => blocks[0]));
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
this.networkSubscription = this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
this.signaturesSubscription = this.stateService.signaturesMode$.subscribe((mode) => {
|
||||
this.signaturesMode = mode;
|
||||
this.updateSignaturesMode();
|
||||
});
|
||||
|
||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||
console.log('query params', params);
|
||||
if (params['sigs'] && ['all', 'interesting', 'none'].includes(params['sigs'])) {
|
||||
this.signaturesOverride = params['sigs'] as SignaturesMode;
|
||||
this.updateSignaturesMode();
|
||||
} else {
|
||||
this.signaturesOverride = null;
|
||||
this.updateSignaturesMode();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
|
||||
this.assetsService.getAssetsMinimalJson$.subscribe((assets) => {
|
||||
@@ -206,12 +233,12 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
const confirmedTxs = this.transactions.filter((tx) => tx.status.confirmed).length;
|
||||
|
||||
this.transactions.forEach((tx) => {
|
||||
tx['@voutLimit'] = true;
|
||||
tx['@vinLimit'] = true;
|
||||
if (tx['addressValue'] !== undefined) {
|
||||
return;
|
||||
}
|
||||
tx['_showSignatures'] = false;
|
||||
tx['_interestingSignatures'] = false;
|
||||
|
||||
if (this.addresses?.length) {
|
||||
const addressIn = tx.vout.map(v => {
|
||||
@@ -293,6 +320,11 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
});
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
if (!tx['_interestingSignatures']) {
|
||||
tx['_interestingSignatures'] = tx['_sigs'].some(sigs => sigs.some(sig => this.sigIsInteresting(sig)));
|
||||
}
|
||||
tx['_showSignatures'] = this.shouldShowSignatures(tx);
|
||||
}
|
||||
|
||||
tx.largeInput = tx.largeInput || tx.vin.some(vin => (vin?.prevout?.value > 1000000000));
|
||||
@@ -541,8 +573,36 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
this.ref.markForCheck();
|
||||
}
|
||||
|
||||
updateSignaturesMode(): void {
|
||||
this.signaturesMode = this.signaturesOverride || this.forceSignaturesMode || this.signaturesPreference || 'interesting';
|
||||
for (const tx of this.transactions) {
|
||||
tx['_showSignatures'] = this.shouldShowSignatures(tx);
|
||||
}
|
||||
}
|
||||
|
||||
showSig(sigs: SigInfo[]): boolean {
|
||||
return this.signaturesMode === 'all' || (this.signaturesMode === 'interesting' && sigs.some(sig => this.sigIsInteresting(sig)));
|
||||
}
|
||||
|
||||
sigIsInteresting(sig: SigInfo): boolean {
|
||||
return sig.sighash !== SighashFlag.DEFAULT && sig.sighash !== SighashFlag.ALL;
|
||||
}
|
||||
|
||||
shouldShowSignatures(tx): boolean {
|
||||
switch (this.signaturesMode) {
|
||||
case 'all':
|
||||
return true;
|
||||
case 'interesting':
|
||||
return tx['_interestingSignatures'];
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.outspendsSubscription.unsubscribe();
|
||||
this.currencyChangeSubscription?.unsubscribe();
|
||||
this.networkSubscription.unsubscribe();
|
||||
this.signaturesSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
@@ -42,6 +42,8 @@ export interface Customization {
|
||||
};
|
||||
}
|
||||
|
||||
export type SignaturesMode = 'all' | 'interesting' | 'none' | null;
|
||||
|
||||
export interface Env {
|
||||
MAINNET_ENABLED: boolean;
|
||||
TESTNET_ENABLED: boolean;
|
||||
@@ -150,6 +152,7 @@ export class StateService {
|
||||
backend$ = new BehaviorSubject<'esplora' | 'electrum' | 'none'>('esplora');
|
||||
networkChanged$ = new ReplaySubject<string>(1);
|
||||
lightningChanged$ = new ReplaySubject<boolean>(1);
|
||||
signaturesMode$: BehaviorSubject<SignaturesMode>;
|
||||
blocksSubject$ = new BehaviorSubject<BlockExtended[]>([]);
|
||||
blocks$: Observable<BlockExtended[]>;
|
||||
transactions$ = new BehaviorSubject<TransactionStripped[]>(null);
|
||||
@@ -332,6 +335,8 @@ export class StateService {
|
||||
this.blocksSubject$.next([]);
|
||||
});
|
||||
|
||||
this.signaturesMode$ = new BehaviorSubject<SignaturesMode>(this.storageService.getValue('signatures-enabled') as SignaturesMode || null);
|
||||
|
||||
this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4;
|
||||
|
||||
this.blocks$ = this.blocksSubject$.pipe(filter(blocks => blocks != null && blocks.length > 0));
|
||||
|
Reference in New Issue
Block a user