Merge branch 'master' into natsoni/high-contrast-theme

This commit is contained in:
natsoni
2024-04-09 12:13:11 +09:00
26 changed files with 721 additions and 412 deletions

View File

@@ -80,7 +80,9 @@
<div class="title float-left">
<h2 i18n="transaction.accelerate|Accelerate button label">Accelerate</h2>
</div>
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" (click)="showAccelerationSummary = false" i18n="hide-accelerator">Hide accelerator</button>
@if (!(isMobile && paymentType === 'cashapp')) {
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" (click)="showAccelerationSummary = false" i18n="hide-accelerator">Hide accelerator</button>
}
<div class="clearfix"></div>
<div class="box">
@@ -456,13 +458,18 @@
</ng-template>
<ng-template #firstSeenRow>
@if (!isLoadingTx && transactionTime !== -1) {
@if (isLoadingTx) {
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
} @else if (transactionTime === -1) {
<tr>
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
<td><span class="skeleton-loader"></span></td>
</tr>
} @else {
<tr>
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
<td><i><app-time kind="since" [time]="transactionTime" [fastRender]="true"></app-time></i></td>
</tr>
} @else {
<ng-container *ngTemplateOutlet="skeletonDetailsRow"></ng-container>
}
</ng-template>
@@ -530,7 +537,7 @@
} @else if (this.mempoolPosition.block >= 7) {
<span [class]="(acceleratorAvailable && accelerateCtaType === 'button') ? 'etaDeepMempool d-flex justify-content-end align-items-center' : ''">
<span i18n="transaction.eta.in-several-hours|Transaction ETA in several hours or more">In several hours (or more)</span>
@if (!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) {
@if (!(isMobile && paymentType === 'cashapp') && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) {
<a [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerateDeepMempool btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
}
</span>
@@ -539,7 +546,7 @@
} @else {
<span class="eta justify-content-end" [class]="(acceleratorAvailable && accelerateCtaType === 'button') ? 'd-flex align-items-center' : ''">
<app-time kind="until" *ngIf="(da$ | async) as da;" [time]="da.adjustedTimeAvg * (this.mempoolPosition.block + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
@if (!tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) {
@if (!(isMobile && paymentType === 'cashapp') && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) {
<a [href]="'/services/accelerator/accelerate?txid=' + tx.txid" class="btn btn-sm accelerate btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
}
</span>
@@ -554,13 +561,13 @@
<ng-template #gogglesRow>
@if (!isLoadingTx) {
@if (((auditStatus && auditStatus.accelerated) || accelerationInfo || (tx && tx.acceleration)) || filters.length) {
@if (isAcceleration || filters.length) {
<tr>
<td class="td-width">
<span class="goggles-icon"><app-svg-images name="goggles" width="100%" height="100%"></app-svg-images></span>
</td>
<td class="wrap-cell">
@if ((auditStatus && auditStatus.accelerated) || accelerationInfo || (tx && tx.acceleration)) {
@if (isAcceleration) {
<span class="badge badge-accelerated mr-1" i18n="transaction.audit.accelerated">Accelerated</span>
}
<ng-container *ngFor="let filter of filters;">
@@ -606,15 +613,15 @@
@if (!isLoadingTx) {
@if ((cpfpInfo && hasEffectiveFeeRate) || accelerationInfo) {
<tr>
@if (tx.acceleration || accelerationInfo) {
@if (isAcceleration) {
<td i18n="transaction.accelerated-fee-rate|Accelerated transaction fee rate">Accelerated fee rate</td>
} @else {
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
}
<td>
<div class="effective-fee-container">
@if (accelerationInfo) {
<app-fee-rate [fee]="accelerationInfo.acceleratedFee" [weight]="accelerationInfo.effectiveVsize * 4"></app-fee-rate>
@if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize)) {
<app-fee-rate [fee]="accelerationInfo.acceleratedFeeRate"></app-fee-rate>
} @else {
<app-fee-rate [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
}

View File

@@ -311,6 +311,13 @@
}
}
.accelerateFullSize {
width: 100%;
height: 100%;
padding: 0.5rem 0.25rem;
background-color: #653b9c;
}
.goggles-icon {
display: block;
width: 2.2em;

View File

@@ -9,7 +9,8 @@ import {
delay,
mergeMap,
tap,
map
map,
retry
} from 'rxjs/operators';
import { Transaction } from '../../interfaces/electrs.interface';
import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest } from 'rxjs';
@@ -93,12 +94,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
adjustedVsize: number | null;
pool: Pool | null;
auditStatus: AuditStatus | null;
isAcceleration: boolean = false;
filters: Filter[] = [];
showCpfpDetails = false;
fetchCpfp$ = new Subject<string>();
fetchRbfHistory$ = new Subject<string>();
fetchCachedTx$ = new Subject<string>();
fetchAcceleration$ = new Subject<string>();
fetchAcceleration$ = new Subject<number>();
fetchMiningInfo$ = new Subject<{ hash: string, height: number, txid: string }>();
isCached: boolean = false;
now = Date.now();
@@ -117,6 +119,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
flowEnabled: boolean;
tooltipPosition: { x: number, y: number };
isMobile: boolean;
paymentType: 'bitcoin' | 'cashapp' = 'bitcoin';
firstLoad = true;
featuresEnabled: boolean;
segwitEnabled: boolean;
@@ -154,6 +158,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
ngOnInit() {
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
if (this.acceleratorAvailable && this.stateService.ref === 'https://cash.app/') {
this.showAccelerationSummary = true;
this.paymentType = 'cashapp';
}
this.enterpriseService.page();
this.websocketService.want(['blocks', 'mempool-blocks']);
@@ -280,9 +289,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
filter(() => this.stateService.env.ACCELERATOR === true),
tap(() => {
this.accelerationInfo = null;
this.setIsAccelerated();
}),
switchMap((blockHash: string) => {
return this.servicesApiService.getAccelerationHistory$({ blockHash });
switchMap((blockHeight: number) => {
return this.servicesApiService.getAccelerationHistory$({ blockHeight });
}),
catchError(() => {
return of(null);
@@ -290,8 +300,12 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
).subscribe((accelerationHistory) => {
for (const acceleration of accelerationHistory) {
if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional')) {
acceleration.acceleratedFee = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + acceleration.feePaid - acceleration.baseFee - acceleration.vsizeFee);
const boostCost = acceleration.boostCost || (acceleration.feePaid - acceleration.baseFee - acceleration.vsizeFee);
acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize;
acceleration.boost = boostCost;
this.accelerationInfo = acceleration;
this.setIsAccelerated();
}
}
});
@@ -312,6 +326,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
map(block => {
return block.extras.pool;
}),
retry({ count: 3, delay: 2000 }),
catchError(() => {
return of(null);
})
@@ -332,18 +347,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
accelerated: isAccelerated,
};
}),
retry({ count: 3, delay: 2000 }),
catchError(() => {
return of(null);
})
) : of(isCoinbase ? { coinbase: true } : null)
]);
}),
catchError(() => {
catchError((e) => {
return of(null);
})
).subscribe(([pool, auditStatus]) => {
this.pool = pool;
this.auditStatus = auditStatus;
this.setIsAccelerated();
});
this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
@@ -475,7 +493,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.getTransactionTime();
}
} else {
this.fetchAcceleration$.next(tx.status.block_hash);
this.fetchAcceleration$.next(tx.status.block_height);
this.fetchMiningInfo$.next({ hash: tx.status.block_hash, height: tx.status.block_height, txid: tx.txid });
this.transactionTime = 0;
}
@@ -537,7 +555,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
} else {
this.audioService.playSound('magic');
}
this.fetchAcceleration$.next(block.id);
this.fetchAcceleration$.next(block.height);
this.fetchMiningInfo$.next({ hash: block.id, height: block.height, txid: this.tx.txid });
}
});
@@ -666,10 +684,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
relatives.reduce((prev, val) => prev + val.fee, 0);
this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
} else {
this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize || this.tx.effectiveFeePerVsize || this.tx.feePerVsize || (this.tx.fee / (this.tx.weight / 4));
}
if (cpfpInfo.acceleration) {
this.tx.acceleration = cpfpInfo.acceleration;
this.setIsAccelerated();
}
this.cpfpInfo = cpfpInfo;
@@ -681,6 +700,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
}
setIsAccelerated() {
console.log(this.tx.acceleration, this.accelerationInfo, this.pool, this.accelerationInfo?.pools);
this.isAcceleration = (this.tx.acceleration || (this.accelerationInfo && this.pool && this.accelerationInfo.pools.some(pool => (pool === this.pool.id || pool?.['pool_unique_id'] === this.pool.id))));
}
setFeatures(): void {
if (this.tx) {
this.segwitEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'segwit');
@@ -720,6 +744,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
resetTransaction() {
if (!this.firstLoad) {
this.stateService.ref = '';
} else {
this.firstLoad = false;
}
this.error = undefined;
this.tx = null;
this.setFeatures();
@@ -742,6 +771,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.pool = null;
this.auditStatus = null;
document.body.scrollTo(0, 0);
this.isAcceleration = false;
this.leaveTransaction();
}
@@ -814,6 +844,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
ngOnDestroy() {
this.stateService.ref = '';
this.subscription.unsubscribe();
this.fetchCpfpSubscription.unsubscribe();
this.fetchRbfSubscription.unsubscribe();