Merge pull request #5243 from mempool/mononaut/hybrid-accelerator-polish

Accelerator polish
This commit is contained in:
softsimon 2024-07-01 18:00:08 +09:00 committed by GitHub
commit dfede7fe25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 221 additions and 157 deletions

View file

@ -280,31 +280,55 @@
<div class="alert alert-mempool mr-6">You are currently on the waitlist for Mempool Accelerator&trade;</div> <div class="alert alert-mempool mr-6">You are currently on the waitlist for Mempool Accelerator&trade;</div>
</div> </div>
<form [class.disabled]="error || showSuccess"> @if (!advancedEnabled) {
<div class="row summary-row"> <form [class.disabled]="error || showSuccess">
<div> <div class="row">
<div class="form-group form-check mb-2"> <div class="col-md">
<div class="float-right"><ng-container *ngTemplateOutlet="customizeButton"></ng-container></div> <div class="form-group form-check mb-2">
<input type="checkbox" [checked]="armed" class="form-check-input" [class.error-shake]="misfire" id="accel" name="accel" (change)="armed = !armed; misfire = false"> <input type="radio" [checked]="selectedOption === 'wait'" class="form-check-input" id="wait" name="accel" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="accel"> <label class="form-check-label d-flex flex-column" for="wait">
<span><b>Accelerate</b> to ~{{ ((userBid + estimate.txSummary.effectiveFee) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB</span> <span class="font-weight-bold">Wait</span>
<span class="checkout-text">Confirmation expected <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time><br> @if (eta.blocks < 7) {
@if (!calculating) { <span class="checkout-text">Confirmation expected <app-time kind="within" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time></span>
<app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span>)
} @else { } @else {
<span class="estimating">Calculating cost...</span> <span class="checkout-text">
<span>Confirmation expected within several hours</span>
</span>
} }
</span> </label>
</label> </div>
<div class="form-group form-check mb-2">
<input type="radio" [checked]="selectedOption === 'accel'" class="form-check-input" id="accel" name="accel" (change)="selectedOptionChanged($event)">
<label class="form-check-label d-flex flex-column" for="accel">
<ng-container *ngTemplateOutlet="accelerateOption; context: {etaInfo}"></ng-container>
</label>
</div>
</div> </div>
</div> </div>
<div class="pie d-none d-lg-flex" *ngIf="!forceMobile"> <div class="row mt-2 mb-2">
<small class="form-text checkout-text mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to {{ etaInfo.hashratePercentage | number : '1.1-1' }}% of miners.</small> <div class="col-sm d-flex flex-row justify-content-center">
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box> <ng-container *ngTemplateOutlet="accelerateButton"></ng-container>
</div>
</div>
</form>
} @else {
<div [class.disabled]="error || showSuccess">
<div class="row summary-row">
<div>
<div class="mb-2">
<div class="d-flex flex-column" for="accel">
<ng-container *ngTemplateOutlet="accelerateOption; context: {etaInfo}"></ng-container>
</div>
</div>
</div>
<div class="pie d-none d-lg-flex">
<small class="form-text checkout-text mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to {{ etaInfo.hashratePercentage | number : '1.1-1' }}% of miners.</small>
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
</div>
<ng-container *ngTemplateOutlet="accelerateButton"></ng-container>
</div> </div>
<ng-container *ngTemplateOutlet="accelerateButton"></ng-container>
</div> </div>
</form> }
</ng-container> </ng-container>
<ng-template #loadingSummary> <ng-template #loadingSummary>
<div class="row"> <div class="row">
@ -338,18 +362,20 @@
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box> <app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
</div> </div>
</div> </div>
@if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) { <div class="payment-area mt-2 p-2" [class.disabled]="error || showSuccess" style="font-size: 14px;">
<div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || error || showSuccess"> <div class="row text-center justify-content-center mx-2">
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" style="width: 200px" (click)="accelerate()"> <p>Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + tx.txid" target="_blank"> {{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}</a></p>
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span>
</button>
</div> </div>
} @else { @if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) {
<div class="payment-area mt-2 p-2" [class.disabled]="error || showSuccess"> <div class="row">
<div class="row text-center justify-content-center mx-2" style="font-size: 14px;"> <div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
<p>Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + tx.txid" target="_blank"> {{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}</a></p> <p>Your account will be debited no more than <span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span></p>
<div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || error || showSuccess">
<ng-container *ngTemplateOutlet="accountPayButton"></ng-container>
</div>
</div>
</div> </div>
} @else {
<div class="row"> <div class="row">
@if (canPayWithBitcoin) { @if (canPayWithBitcoin) {
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center"> <div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
@ -376,8 +402,8 @@
</div> </div>
} }
</div> </div>
</div> }
} </div>
</ng-container> </ng-container>
<ng-template #loadingCheckout> <ng-template #loadingCheckout>
<div class="row"> <div class="row">
@ -486,13 +512,24 @@
} }
</div> </div>
<ng-template #accelerateOption let-etaInfo="etaInfo">
<span><b>Accelerate</b> to ~{{ ((userBid + estimate.txSummary.effectiveFee) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB <ng-container *ngTemplateOutlet="customizeButton"></ng-container></span>
<span class="checkout-text">Confirmation expected <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time><br>
@if (!calculating) {
<app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small>&nbsp;<span class="symbol" i18n="shared.sats">sats</span></span>)
} @else {
<span class="estimating">Calculating cost...</span>
}
</span>
</ng-template>
<ng-template #customizeButton> <ng-template #customizeButton>
<button type="button" *ngIf="advancedEnabled" class="btn btn-sm btn-outline-info btn-small-height ml-3" (click)="moveToStep('quote')" i18n="accelerator.customize">customize</button> <button type="button" *ngIf="advancedEnabled" class="btn btn-sm btn-outline-info btn-small-height ml-2" (click)="moveToStep('quote')" i18n="accelerator.customize">customize</button>
</ng-template> </ng-template>
<ng-template #accelerateButton> <ng-template #accelerateButton>
@if (isLoggedIn() || canPayWithBitcoin || canPayWithCashapp) { @if (isLoggedIn() || canPayWithBitcoin || canPayWithCashapp) {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.grayOut]="!canPay || (!armed && step === 'summary') || calculating" style="width: 200px" (click)="accelerate()"> <button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.disabled]="!canPay || calculating || (!advancedEnabled && selectedOption !== 'accel')" style="width: 200px" (click)="moveToStep('checkout')">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px"> <img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Accelerate</span> <span>Accelerate</span>
</button> </button>
@ -502,4 +539,18 @@
<span>Coming soon</span> <span>Coming soon</span>
</button> </button>
} }
</ng-template>
<ng-template #accountPayButton>
@if (isLoggedIn()) {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.disabled]="!canPay || calculating" style="width: 200px" (click)="accelerateWithMempoolAccount()">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Pay</span>
</button>
} @else {
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center disabled" style="width: 200px">
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
<span>Coming soon</span>
</button>
}
</ng-template> </ng-template>

View file

@ -189,22 +189,3 @@
flex-direction: column; flex-direction: column;
} }
} }
@keyframes box-shake {
0% { transform: rotate(0deg); }
10% { transform: rotate(-8deg); }
20% { transform: rotate(8deg); }
30% { transform: rotate(-8deg); }
40% { transform: rotate(8deg); }
50% { transform: rotate(-8deg); }
60% { transform: rotate(8deg); }
70% { transform: rotate(-8deg); }
80% { transform: rotate(8deg); }
90% { transform: rotate(-8deg); }
100% { transform: rotate(0deg); }
}
.error-shake {
box-shadow: 0 0 10px 2px var(--danger);
animation: box-shake 1.5s ease-in-out;
}

View file

@ -62,8 +62,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
@Output() changeMode = new EventEmitter<boolean>(); @Output() changeMode = new EventEmitter<boolean>();
calculating = true; calculating = true;
armed = false; selectedOption: 'wait' | 'accel';
misfire = false;
error = ''; error = '';
math = Math; math = Math;
isMobile: boolean = window.innerWidth <= 767.98; isMobile: boolean = window.innerWidth <= 767.98;
@ -150,7 +149,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
moveToStep(step: CheckoutStep) { moveToStep(step: CheckoutStep) {
this._step = step; this._step = step;
this.misfire = false;
if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) { if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) {
this.fetchEstimate(); this.fetchEstimate();
} }
@ -265,28 +263,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
} }
} }
/**
* Advanced mode acceleration button clicked
*/
accelerate(): void {
if (this.canPay && !this.calculating) {
if ((!this.armed && this.step === 'summary')) {
this.misfire = true;
} else {
if (this.isLoggedIn()) {
this.accelerateWithMempoolAccount();
} else {
this.armed = true;
this.moveToStep('checkout');
}
}
}
}
/** /**
* Account-based acceleration request * Account-based acceleration request
*/ */
accelerateWithMempoolAccount(): void { accelerateWithMempoolAccount(): void {
if (!this.canPay || this.calculating) {
return;
}
if (this.accelerationSubscription) { if (this.accelerationSubscription) {
this.accelerationSubscription.unsubscribe(); this.accelerationSubscription.unsubscribe();
} }
@ -469,6 +452,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return auth !== null; return auth !== null;
} }
/**
* UI events
*/
selectedOptionChanged(event) {
this.selectedOption = event.target.id;
}
get step() { get step() {
return this._step; return this._step;
} }

View file

@ -122,7 +122,7 @@
<span class="explainer">&nbsp;</span> <span class="explainer">&nbsp;</span>
} @else if (showAccelerationSummary) { } @else if (showAccelerationSummary) {
<ng-container *ngIf="(ETA$ | async) as eta;"> <ng-container *ngIf="(ETA$ | async) as eta;">
<app-accelerate-checkout *ngIf="(da$ | async) as da;" [cashappEnabled]="accelerationEligible" [advancedEnabled]="false" [forceMobile]="true" [tx]="tx" [miningStats]="miningStats" [eta]="eta" [scrollEvent]="scrollIntoAccelPreview" class="h-100 w-100"></app-accelerate-checkout> <app-accelerate-checkout *ngIf="(da$ | async) as da;" [cashappEnabled]="cashappEligible" [advancedEnabled]="false" [forceMobile]="true" [tx]="tx" [miningStats]="miningStats" [eta]="eta" [scrollEvent]="scrollIntoAccelPreview" class="h-100 w-100"></app-accelerate-checkout>
</ng-container> </ng-container>
} @else { } @else {
@if (tx?.acceleration && !tx.status?.confirmed) { @if (tx?.acceleration && !tx.status?.confirmed) {

View file

@ -21,7 +21,7 @@ import { AudioService } from '../../services/audio.service';
import { ApiService } from '../../services/api.service'; import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service'; import { SeoService } from '../../services/seo.service';
import { seoDescriptionNetwork } from '../../shared/common.utils'; import { seoDescriptionNetwork } from '../../shared/common.utils';
import { Filter } from '../../shared/filters.utils'; import { Filter, TransactionFlags } from '../../shared/filters.utils';
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '../../interfaces/node-api.interface'; import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '../../interfaces/node-api.interface';
import { PriceService } from '../../services/price.service'; import { PriceService } from '../../services/price.service';
import { ServicesApiServices } from '../../services/services-api.service'; import { ServicesApiServices } from '../../services/services-api.service';
@ -30,7 +30,7 @@ import { ZONE_SERVICE } from '../../injection-tokens';
import { TrackerStage } from './tracker-bar.component'; import { TrackerStage } from './tracker-bar.component';
import { MiningService, MiningStats } from '../../services/mining.service'; import { MiningService, MiningStats } from '../../services/mining.service';
import { ETA, EtaService } from '../../services/eta.service'; import { ETA, EtaService } from '../../services/eta.service';
import { getUnacceleratedFeeRate } from '../../shared/transaction.utils'; import { getTransactionFlags, getUnacceleratedFeeRate } from '../../shared/transaction.utils';
interface Pool { interface Pool {
id: number; id: number;
@ -117,8 +117,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
hasEffectiveFeeRate: boolean; hasEffectiveFeeRate: boolean;
accelerateCtaType: 'alert' | 'button' = 'button'; accelerateCtaType: 'alert' | 'button' = 'button';
acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === ''; acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === '';
accelerationEligible: boolean = false; eligibleForAcceleration: boolean = false;
showAccelerationSummary = false;
accelerationFlowCompleted = false; accelerationFlowCompleted = false;
scrollIntoAccelPreview = false; scrollIntoAccelPreview = false;
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true; auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
@ -155,11 +154,6 @@ export class TrackerComponent implements OnInit, OnDestroy {
this.miningStats = stats; this.miningStats = stats;
}); });
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('cash_request_id')) {
this.showAccelerationSummary = true;
}
this.enterpriseService.page(); this.enterpriseService.page();
this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => { this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => {
@ -267,6 +261,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
if (!this.tx) { if (!this.tx) {
this.tx = tx; this.tx = tx;
this.checkAccelerationEligibility();
this.isCached = true; this.isCached = true;
if (tx.fee === undefined) { if (tx.fee === undefined) {
this.tx.fee = 0; this.tx.fee = 0;
@ -385,20 +380,9 @@ export class TrackerComponent implements OnInit, OnDestroy {
this.trackerStage = 'replaced'; this.trackerStage = 'replaced';
} }
if (!this.mempoolPosition.accelerated) { if (this.mempoolPosition.accelerated && this.showAccelerationSummary) {
if (!this.accelerationFlowCompleted && !this.showAccelerationSummary && this.mempoolPosition.block > 0) {
this.showAccelerationSummary = true;
this.miningService.getMiningStats('1w').subscribe(stats => {
this.miningStats = stats;
});
}
if (txPosition.position?.block > 0) {
this.accelerationEligible = true;
}
} else if (this.showAccelerationSummary) {
setTimeout(() => { setTimeout(() => {
this.accelerationFlowCompleted = true; this.accelerationFlowCompleted = true;
this.showAccelerationSummary = false;
}, 2000); }, 2000);
} }
} }
@ -462,6 +446,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
this.seoService.clearSoft404(); this.seoService.clearSoft404();
this.tx = tx; this.tx = tx;
this.checkAccelerationEligibility();
this.isCached = false; this.isCached = false;
if (tx.fee === undefined) { if (tx.fee === undefined) {
this.tx.fee = 0; this.tx.fee = 0;
@ -744,8 +729,9 @@ export class TrackerComponent implements OnInit, OnDestroy {
} }
this.enterpriseService.goal(8); this.enterpriseService.goal(8);
this.accelerationFlowCompleted = false; this.accelerationFlowCompleted = false;
this.showAccelerationSummary = true && this.acceleratorAvailable; if (this.showAccelerationSummary) {
this.scrollIntoAccelPreview = true; this.scrollIntoAccelPreview = true;
}
return false; return false;
} }
@ -753,6 +739,31 @@ export class TrackerComponent implements OnInit, OnDestroy {
return this.isLoadingTx || this.loadingCachedTx || this.loadingPosition; return this.isLoadingTx || this.loadingCachedTx || this.loadingPosition;
} }
checkAccelerationEligibility() {
if (this.tx) {
this.tx.flags = getTransactionFlags(this.tx);
const replaceableInputs = (this.tx.flags & (TransactionFlags.sighash_none | TransactionFlags.sighash_acp)) > 0n;
const highSigop = (this.tx.sigops * 20) > this.tx.weight;
this.eligibleForAcceleration = !replaceableInputs && !highSigop;
} else {
this.eligibleForAcceleration = false;
}
}
get cashappEligible(): boolean {
return this.mempoolPosition?.block > 0;
}
get showAccelerationSummary(): boolean {
return (
this.tx
&& !this.tx.acceleration
&& this.acceleratorAvailable
&& this.eligibleForAcceleration
&& !this.accelerationFlowCompleted
);
}
resetTransaction() { resetTransaction() {
this.error = undefined; this.error = undefined;
this.tx = null; this.tx = null;
@ -778,7 +789,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
this.pool = null; this.pool = null;
this.auditStatus = null; this.auditStatus = null;
this.accelerationPositions = null; this.accelerationPositions = null;
this.accelerationEligible = false; this.eligibleForAcceleration = false;
this.trackerStage = 'waiting'; this.trackerStage = 'waiting';
document.body.scrollTo(0, 0); document.body.scrollTo(0, 0);
this.leaveTransaction(); this.leaveTransaction();

View file

@ -74,38 +74,9 @@
<ng-template [ngIf]="!isLoadingTx && !error"> <ng-template [ngIf]="!isLoadingTx && !error">
<!-- Accelerator --> <!-- CPFP Details -->
<ng-container *ngIf="!tx?.status?.confirmed && showAccelerationSummary">
<br>
<div class="title float-left">
<h2 i18n="transaction.accelerate|Accelerate button label">Accelerate</h2>
</div>
<button type="button" class="btn btn-outline-info flow-toggle btn-sm float-right" (click)="closeAccelerator()" i18n="hide-diagram">Hide accelerator</button>
<button *ngIf="hasAccelerationDetails" class="btn btn-sm btn-outline-info float-right ml-2" (click)="showAccelerationDetails = !showAccelerationDetails">Details</button>
<div class="clearfix"></div>
<ng-container *ngIf="(ETA$ | async) as eta;">
<app-accelerate-checkout
*ngIf="(da$ | async) as da;"
[cashappEnabled]="accelerationEligible"
[advancedEnabled]="true"
[tx]="tx"
[eta]="eta"
[miningStats]="miningStats"
[scrollEvent]="scrollIntoAccelPreview"
[showDetails]="showAccelerationDetails"
[noCTA]="true"
(hasDetails)="setHasAccelerationDetails($event)"
class="h-100 w-100"
></app-accelerate-checkout>
</ng-container>
</ng-container>
<ng-template [ngIf]="showCpfpDetails"> <ng-template [ngIf]="showCpfpDetails">
<br> <br>
<h2 class="text-left">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="xs"></fa-icon></h2> <h2 class="text-left">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="xs"></fa-icon></h2>
<div class="box cpfp-details"> <div class="box cpfp-details">
<table class="table table-fixed table-borderless table-striped"> <table class="table table-fixed table-borderless table-striped">
@ -161,6 +132,35 @@
</div> </div>
</ng-template> </ng-template>
<!-- Accelerator -->
<ng-container *ngIf="!tx?.status?.confirmed && showAccelerationSummary">
<br>
<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)="closeAccelerator()" i18n="hide-diagram">Hide accelerator</button>
<button *ngIf="hasAccelerationDetails" class="btn btn-sm btn-outline-info details-button float-right ml-2" (click)="showAccelerationDetails = !showAccelerationDetails">Details</button>
<div class="clearfix"></div>
<ng-container *ngIf="(ETA$ | async) as eta;">
<app-accelerate-checkout
*ngIf="(da$ | async) as da;"
[cashappEnabled]="cashappEligible"
[advancedEnabled]="true"
[tx]="tx"
[eta]="eta"
[miningStats]="miningStats"
[scrollEvent]="scrollIntoAccelPreview"
[showDetails]="showAccelerationDetails"
[noCTA]="true"
(hasDetails)="setHasAccelerationDetails($event)"
class="h-100 w-100"
></app-accelerate-checkout>
</ng-container>
</ng-container>
<br> <br>
<ng-container *ngIf="rbfInfo"> <ng-container *ngIf="rbfInfo">
@ -551,18 +551,18 @@
<td> <td>
<ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton"> <ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton">
@if (eta.blocks >= 7) { @if (eta.blocks >= 7) {
<span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary) ? 'etaDeepMempool d-flex justify-content-end align-items-center' : ''"> <span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) ? '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> <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' && !showAccelerationSummary) { @if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) {
<a class="btn btn-sm accelerateDeepMempool btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a> <a class="btn btn-sm accelerateDeepMempool btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
} }
</span> </span>
} @else if (network === 'liquid' || network === 'liquidtestnet') { } @else if (network === 'liquid' || network === 'liquidtestnet') {
<app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time> <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
} @else { } @else {
<span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary) ? 'etaDeepMempool d-flex justify-content-end align-items-center' : ''"> <span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) ? 'etaDeepMempool d-flex justify-content-end align-items-center' : ''">
<app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time> <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
@if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary) { @if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) {
<a class="btn btn-sm accelerateDeepMempool btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a> <a class="btn btn-sm accelerateDeepMempool btn-small-height" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
} }
</span> </span>

View file

@ -24,8 +24,8 @@ import { SeoService } from '../../services/seo.service';
import { StorageService } from '../../services/storage.service'; import { StorageService } from '../../services/storage.service';
import { seoDescriptionNetwork } from '../../shared/common.utils'; import { seoDescriptionNetwork } from '../../shared/common.utils';
import { getTransactionFlags, getUnacceleratedFeeRate } from '../../shared/transaction.utils'; import { getTransactionFlags, getUnacceleratedFeeRate } from '../../shared/transaction.utils';
import { Filter, toFilters } from '../../shared/filters.utils'; import { Filter, TransactionFlags, toFilters } from '../../shared/filters.utils';
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition, SinglePoolStats } from '../../interfaces/node-api.interface'; import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '../../interfaces/node-api.interface';
import { LiquidUnblinding } from './liquid-ublinding'; import { LiquidUnblinding } from './liquid-ublinding';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { PriceService } from '../../services/price.service'; import { PriceService } from '../../services/price.service';
@ -137,12 +137,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
hasEffectiveFeeRate: boolean; hasEffectiveFeeRate: boolean;
accelerateCtaType: 'alert' | 'button' = 'button'; accelerateCtaType: 'alert' | 'button' = 'button';
acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === ''; acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === '';
showAccelerationSummary = false; eligibleForAcceleration: boolean = false;
forceAccelerationSummary = false;
hideAccelerationSummary = false;
accelerationFlowCompleted = false;
showAccelerationDetails = false; showAccelerationDetails = false;
hasAccelerationDetails = false; hasAccelerationDetails = false;
accelerationFlowCompleted = false;
scrollIntoAccelPreview = false; scrollIntoAccelPreview = false;
accelerationEligible = false; cashappEligible = false;
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true; auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
@ViewChild('graphContainer') @ViewChild('graphContainer')
@ -173,11 +175,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.enterpriseService.page(); this.enterpriseService.page();
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('cash_request_id')) { this.forceAccelerationSummary = !!urlParams.get('cash_request_id');
this.showAccelerationSummary = true;
}
if (!this.stateService.isLiquid) { this.hideAccelerationSummary = this.storageService.getValue('hide-accelerator-pref') == 'true';
if (!this.stateService.isLiquid()) {
this.miningService.getMiningStats('1w').subscribe(stats => { this.miningService.getMiningStats('1w').subscribe(stats => {
this.miningStats = stats; this.miningStats = stats;
}); });
@ -414,18 +416,17 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.stateService.network === '') { if (this.stateService.network === '') {
if (!this.mempoolPosition.accelerated) { if (!this.mempoolPosition.accelerated) {
if (!this.accelerationFlowCompleted && !this.showAccelerationSummary) { if (!this.accelerationFlowCompleted && !this.hideAccelerationSummary && !this.showAccelerationSummary) {
this.showAccelerationSummary = true;
this.miningService.getMiningStats('1w').subscribe(stats => { this.miningService.getMiningStats('1w').subscribe(stats => {
this.miningStats = stats; this.miningStats = stats;
}); });
} }
if (txPosition.position?.block > 0 && this.tx.weight < 4000) { if (txPosition.position?.block > 0 && this.tx.weight < 4000) {
this.accelerationEligible = true; this.cashappEligible = true;
} }
} else if (this.showAccelerationSummary) { } else if (this.showAccelerationSummary) {
setTimeout(() => { setTimeout(() => {
this.closeAccelerator(); this.accelerationFlowCompleted = true;
}, 2000); }, 2000);
} }
} }
@ -715,8 +716,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
document.location.hash = '#accelerate'; document.location.hash = '#accelerate';
this.enterpriseService.goal(8); this.enterpriseService.goal(8);
this.accelerationFlowCompleted = false; this.openAccelerator();
this.showAccelerationSummary = this.acceleratorAvailable;
this.scrollIntoAccelPreview = true; this.scrollIntoAccelPreview = true;
return false; return false;
} }
@ -778,7 +778,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
} }
if (!this.isAcceleration && this.fragmentParams.has('accelerate')) { if (!this.isAcceleration && this.fragmentParams.has('accelerate')) {
this.onAccelerateClicked(); this.forceAccelerationSummary = true;
} }
this.txChanged$.next(true); this.txChanged$.next(true);
@ -797,10 +797,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.isAcceleration) { if (this.isAcceleration) {
if (initialState) { if (initialState) {
this.accelerationFlowCompleted = true; this.accelerationFlowCompleted = true;
this.showAccelerationSummary = false;
} else if (this.showAccelerationSummary) { } else if (this.showAccelerationSummary) {
setTimeout(() => { setTimeout(() => {
this.closeAccelerator(); this.accelerationFlowCompleted = true;
}, 2000); }, 2000);
} }
} }
@ -821,6 +820,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.rbfEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'rbf'); this.rbfEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'rbf');
this.tx.flags = getTransactionFlags(this.tx); this.tx.flags = getTransactionFlags(this.tx);
this.filters = this.tx.flags ? toFilters(this.tx.flags).filter(f => f.txPage) : []; this.filters = this.tx.flags ? toFilters(this.tx.flags).filter(f => f.txPage) : [];
this.checkAccelerationEligibility();
} else { } else {
this.segwitEnabled = false; this.segwitEnabled = false;
this.taprootEnabled = false; this.taprootEnabled = false;
@ -829,6 +829,16 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.featuresEnabled = this.segwitEnabled || this.taprootEnabled || this.rbfEnabled; this.featuresEnabled = this.segwitEnabled || this.taprootEnabled || this.rbfEnabled;
} }
checkAccelerationEligibility() {
if (this.tx && this.tx.flags) {
const replaceableInputs = (this.tx.flags & (TransactionFlags.sighash_none | TransactionFlags.sighash_acp)) > 0n;
const highSigop = (this.tx.sigops * 20) > this.tx.weight;
this.eligibleForAcceleration = !replaceableInputs && !highSigop;
} else {
this.eligibleForAcceleration = false;
}
}
isAuditAvailable(blockHeight: number): boolean { isAuditAvailable(blockHeight: number): boolean {
if (!this.auditEnabled) { if (!this.auditEnabled) {
return false; return false;
@ -873,7 +883,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.showCpfpDetails = false; this.showCpfpDetails = false;
this.showAccelerationDetails = false; this.showAccelerationDetails = false;
this.accelerationInfo = null; this.accelerationInfo = null;
this.accelerationEligible = false; this.cashappEligible = false;
this.txInBlockIndex = null; this.txInBlockIndex = null;
this.mempoolPosition = null; this.mempoolPosition = null;
this.pool = null; this.pool = null;
@ -882,6 +892,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
document.body.scrollTo(0, 0); document.body.scrollTo(0, 0);
this.isAcceleration = false; this.isAcceleration = false;
this.isAccelerated$.next(this.isAcceleration); this.isAccelerated$.next(this.isAcceleration);
this.eligibleForAcceleration = false;
this.leaveTransaction(); this.leaveTransaction();
} }
@ -890,11 +901,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.stateService.markBlock$.next({}); this.stateService.markBlock$.next({});
} }
closeAccelerator(): void {
this.accelerationFlowCompleted = true;
this.showAccelerationSummary = false;
}
roundToOneDecimal(cpfpTx: any): number { roundToOneDecimal(cpfpTx: any): number {
return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1); return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1);
} }
@ -963,6 +969,32 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
return auth !== null; return auth !== null;
} }
closeAccelerator(): void {
document.location.hash = '';
this.hideAccelerationSummary = true;
this.forceAccelerationSummary = false;
this.storageService.setValue('hide-accelerator-pref', 'true');
}
openAccelerator(): void {
this.accelerationFlowCompleted = false;
this.hideAccelerationSummary = false;
this.storageService.setValue('hide-accelerator-pref', 'false');
}
get showAccelerationSummary(): boolean {
return (
this.tx
&& !this.tx.acceleration
&& this.acceleratorAvailable
&& this.eligibleForAcceleration
&& (
(!this.hideAccelerationSummary && !this.accelerationFlowCompleted)
|| this.forceAccelerationSummary
)
);
}
ngOnDestroy() { ngOnDestroy() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
this.fetchCpfpSubscription.unsubscribe(); this.fetchCpfpSubscription.unsubscribe();

View file

@ -1 +0,0 @@
<svg id="レイヤー_1" data-name="レイヤー 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236.18 102.3"><defs><style>.cls-1{fill:#cedc21;}.cls-2{fill:#51b13e;}.cls-3{fill:#1e7a44;}.cls-4{fill:#fff;}</style></defs><title>btcpay3</title><path class="cls-1" d="M38.55,201.73a6,6,0,0,1-6-6V105.44a6,6,0,0,1,12,0v90.29A6,6,0,0,1,38.55,201.73Z" transform="translate(-32.55 -99.43)"/><path class="cls-2" d="M38.56,201.73A6,6,0,0,1,36,190.31l36.18-17.17L35,145.76a6,6,0,1,1,7.11-9.66l45.24,33.33a6,6,0,0,1-1,10.25L41.13,201.15A5.9,5.9,0,0,1,38.56,201.73Z" transform="translate(-32.55 -99.43)"/><path class="cls-1" d="M38.56,166.24A6,6,0,0,1,35,155.41L72.16,128,36,110.86A6,6,0,1,1,41.13,100l45.24,21.47a6,6,0,0,1,1,10.25L42.11,165.07A6,6,0,0,1,38.56,166.24Z" transform="translate(-32.55 -99.43)"/><polygon class="cls-3" points="12 38.46 12 63.84 29.21 51.16 12 38.46"/><rect class="cls-4" y="27.82" width="12" height="29.25"/><path class="cls-1" d="M44.55,105.44a6,6,0,0,0-12,0V181h12Z" transform="translate(-32.55 -99.43)"/><path class="cls-4" d="M123.92,149.86c3.84,1.09,6,4.57,6,8.93,0,6.81-4.15,10-9.82,10H107.14V132.4h11.43c5.56,0,9.87,2.65,9.87,9.56C128.44,145.44,127,148.66,123.92,149.86Zm-5.3-.89c4.1,0,7.43-1.45,7.43-7.06s-3.43-7.17-7.59-7.17h-8.88V149Zm1.3,17.41c4.15,0,7.48-2.18,7.48-7.59,0-5.82-3.79-7.58-8.52-7.58h-9.3v15.17Z" transform="translate(-32.55 -99.43)"/><path class="cls-4" d="M154.21,132.4v2.23h-10v34.14h-2.44V134.63h-10V132.4Z" transform="translate(-32.55 -99.43)"/><path class="cls-4" d="M171.3,131.88c6.19,0,11.8,3.12,13.57,10.55h-2.34c-1.66-6.08-6.55-8.32-11.28-8.32-8.57,0-13.09,7-13.09,16.47,0,10,4.52,16.37,13.14,16.37,5.1,0,9.67-2.29,11.44-9h2.33a13.6,13.6,0,0,1-13.77,11.33c-9.71,0-15.53-7.17-15.53-18.71C155.77,139.52,161.9,131.88,171.3,131.88Z" transform="translate(-32.55 -99.43)"/><path class="cls-4" d="M203.36,132.4c6.29,0,10.86,4.1,10.86,12,0,7.48-4.57,12-10.86,12H193.8v12.32h-2.39V132.4Zm0,21.66c4.63,0,8.42-3,8.42-9.66s-3.64-9.71-8.42-9.71H193.8v19.37Z" transform="translate(-32.55 -99.43)"/><path class="cls-4" d="M214.64,168.77v-.31l14.49-36.16h1.1l14.34,36.16v.31H242l-4.1-10.5H221.34l-4.1,10.5Zm15-32.16L222.17,156h14.91Z" transform="translate(-32.55 -99.43)"/><path class="cls-4" d="M266.13,132.4h2.6v.36L257.4,153.65v15.12h-2.49V153.65l-11.38-20.94v-.31h2.65l4.93,9.25,5,9.61h0l5-9.61Z" transform="translate(-32.55 -99.43)"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB