Merge pull request #5243 from mempool/mononaut/hybrid-accelerator-polish
Accelerator polish
This commit is contained in:
		
						commit
						dfede7fe25
					
				| @ -280,31 +280,55 @@ | ||||
|         <div class="alert alert-mempool mr-6">You are currently on the waitlist for Mempool Accelerator™</div> | ||||
|       </div> | ||||
| 
 | ||||
|       @if (!advancedEnabled) { | ||||
|         <form [class.disabled]="error || showSuccess"> | ||||
|         <div class="row summary-row"> | ||||
|           <div> | ||||
|           <div class="row"> | ||||
|             <div class="col-md"> | ||||
|               <div class="form-group form-check mb-2"> | ||||
|               <div class="float-right"><ng-container *ngTemplateOutlet="customizeButton"></ng-container></div> | ||||
|               <input type="checkbox" [checked]="armed" class="form-check-input" [class.error-shake]="misfire" id="accel" name="accel" (change)="armed = !armed; misfire = false"> | ||||
|               <label class="form-check-label d-flex flex-column" for="accel"> | ||||
|                 <span><b>Accelerate</b> to ~{{ ((userBid + estimate.txSummary.effectiveFee) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} sat/vB</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> <span class="symbol" i18n="shared.sats">sats</span></span>) | ||||
|                 <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="wait"> | ||||
|                   <span class="font-weight-bold">Wait</span> | ||||
|                   @if (eta.blocks < 7) { | ||||
|                     <span class="checkout-text">Confirmation expected <app-time kind="within" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time></span> | ||||
|                   } @else { | ||||
|                     <span class="estimating">Calculating cost...</span> | ||||
|                   } | ||||
|                     <span class="checkout-text"> | ||||
|                       <span>Confirmation expected within several hours</span> | ||||
|                     </span> | ||||
|                   } | ||||
|                 </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 class="pie d-none d-lg-flex" *ngIf="!forceMobile"> | ||||
|           </div> | ||||
|           <div class="row mt-2 mb-2"> | ||||
|             <div class="col-sm d-flex flex-row justify-content-center"> | ||||
|               <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> | ||||
|       </form> | ||||
|         </div> | ||||
|       } | ||||
|     </ng-container> | ||||
|     <ng-template #loadingSummary> | ||||
|       <div class="row"> | ||||
| @ -338,18 +362,20 @@ | ||||
|           <app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box> | ||||
|         </div> | ||||
|       </div> | ||||
|       @if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) { | ||||
|         <div class="d-flex justify-content-center" [class.grayOut]="!canPayWithBalance || error || showSuccess"> | ||||
|           <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()"> | ||||
|             <img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px"> | ||||
|             <span>Accelerate</span> | ||||
|           </button> | ||||
|         </div> | ||||
|       } @else { | ||||
|         <div class="payment-area mt-2 p-2" [class.disabled]="error || showSuccess"> | ||||
|           <div class="row text-center justify-content-center mx-2" style="font-size: 14px;"> | ||||
|       <div class="payment-area mt-2 p-2" [class.disabled]="error || showSuccess" style="font-size: 14px;"> | ||||
|         <div class="row text-center justify-content-center mx-2"> | ||||
|           <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> | ||||
|         </div> | ||||
|         @if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) { | ||||
|           <div class="row"> | ||||
|             <div class="col-sm text-center d-flex flex-column justify-content-center align-items-center"> | ||||
|               <p>Your account will be debited no more than <span><small style="font-family: monospace;">{{ cost | number }}</small> <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> | ||||
|         } @else { | ||||
|           <div class="row"> | ||||
|             @if (canPayWithBitcoin) { | ||||
|               <div class="col-sm text-center d-flex flex-column justify-content-center align-items-center"> | ||||
| @ -376,8 +402,8 @@ | ||||
|               </div> | ||||
|             } | ||||
|           </div> | ||||
|         </div> | ||||
|         } | ||||
|       </div> | ||||
|     </ng-container> | ||||
|     <ng-template #loadingCheckout> | ||||
|       <div class="row"> | ||||
| @ -486,13 +512,24 @@ | ||||
|   } | ||||
| </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> <span class="symbol" i18n="shared.sats">sats</span></span>) | ||||
|     } @else { | ||||
|       <span class="estimating">Calculating cost...</span> | ||||
|     } | ||||
|   </span> | ||||
| </ng-template> | ||||
| 
 | ||||
| <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 #accelerateButton> | ||||
|   @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"> | ||||
|       <span>Accelerate</span> | ||||
|     </button> | ||||
| @ -503,3 +540,17 @@ | ||||
|     </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> | ||||
| @ -189,22 +189,3 @@ | ||||
|     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; | ||||
| } | ||||
| @ -62,8 +62,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|   @Output() changeMode = new EventEmitter<boolean>(); | ||||
| 
 | ||||
|   calculating = true; | ||||
|   armed = false; | ||||
|   misfire = false; | ||||
|   selectedOption: 'wait' | 'accel'; | ||||
|   error = ''; | ||||
|   math = Math; | ||||
|   isMobile: boolean = window.innerWidth <= 767.98; | ||||
| @ -150,7 +149,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
| 
 | ||||
|   moveToStep(step: CheckoutStep) { | ||||
|     this._step = step; | ||||
|     this.misfire = false; | ||||
|     if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) { | ||||
|       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 | ||||
|    */ | ||||
|   accelerateWithMempoolAccount(): void { | ||||
|     if (!this.canPay || this.calculating) { | ||||
|       return; | ||||
|     } | ||||
|     if (this.accelerationSubscription) { | ||||
|       this.accelerationSubscription.unsubscribe(); | ||||
|     } | ||||
| @ -469,6 +452,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|     return auth !== null; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * UI events | ||||
|    */ | ||||
|   selectedOptionChanged(event) { | ||||
|     this.selectedOption = event.target.id; | ||||
|   } | ||||
| 
 | ||||
|   get step() { | ||||
|     return this._step; | ||||
|   } | ||||
|  | ||||
| @ -122,7 +122,7 @@ | ||||
|         <span class="explainer"> </span> | ||||
|       } @else if (showAccelerationSummary) { | ||||
|         <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> | ||||
|       } @else { | ||||
|         @if (tx?.acceleration && !tx.status?.confirmed) { | ||||
|  | ||||
| @ -21,7 +21,7 @@ import { AudioService } from '../../services/audio.service'; | ||||
| import { ApiService } from '../../services/api.service'; | ||||
| import { SeoService } from '../../services/seo.service'; | ||||
| 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 { PriceService } from '../../services/price.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 { MiningService, MiningStats } from '../../services/mining.service'; | ||||
| import { ETA, EtaService } from '../../services/eta.service'; | ||||
| import { getUnacceleratedFeeRate } from '../../shared/transaction.utils'; | ||||
| import { getTransactionFlags, getUnacceleratedFeeRate } from '../../shared/transaction.utils'; | ||||
| 
 | ||||
| interface Pool { | ||||
|   id: number; | ||||
| @ -117,8 +117,7 @@ export class TrackerComponent implements OnInit, OnDestroy { | ||||
|   hasEffectiveFeeRate: boolean; | ||||
|   accelerateCtaType: 'alert' | 'button' = 'button'; | ||||
|   acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === ''; | ||||
|   accelerationEligible: boolean = false; | ||||
|   showAccelerationSummary = false; | ||||
|   eligibleForAcceleration: boolean = false; | ||||
|   accelerationFlowCompleted = false; | ||||
|   scrollIntoAccelPreview = false; | ||||
|   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; | ||||
|     }); | ||||
| 
 | ||||
|     const urlParams = new URLSearchParams(window.location.search); | ||||
|     if (urlParams.get('cash_request_id')) { | ||||
|       this.showAccelerationSummary = true; | ||||
|     } | ||||
| 
 | ||||
|     this.enterpriseService.page(); | ||||
| 
 | ||||
|     this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => { | ||||
| @ -267,6 +261,7 @@ export class TrackerComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|       if (!this.tx) { | ||||
|         this.tx = tx; | ||||
|         this.checkAccelerationEligibility(); | ||||
|         this.isCached = true; | ||||
|         if (tx.fee === undefined) { | ||||
|           this.tx.fee = 0; | ||||
| @ -385,20 +380,9 @@ export class TrackerComponent implements OnInit, OnDestroy { | ||||
|             this.trackerStage = 'replaced'; | ||||
|           } | ||||
| 
 | ||||
|           if (!this.mempoolPosition.accelerated) { | ||||
|             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) { | ||||
|           if (this.mempoolPosition.accelerated && this.showAccelerationSummary) { | ||||
|             setTimeout(() => { | ||||
|               this.accelerationFlowCompleted = true; | ||||
|               this.showAccelerationSummary = false; | ||||
|             }, 2000); | ||||
|           } | ||||
|         } | ||||
| @ -462,6 +446,7 @@ export class TrackerComponent implements OnInit, OnDestroy { | ||||
|           this.seoService.clearSoft404(); | ||||
| 
 | ||||
|           this.tx = tx; | ||||
|           this.checkAccelerationEligibility(); | ||||
|           this.isCached = false; | ||||
|           if (tx.fee === undefined) { | ||||
|             this.tx.fee = 0; | ||||
| @ -744,8 +729,9 @@ export class TrackerComponent implements OnInit, OnDestroy { | ||||
|     } | ||||
|     this.enterpriseService.goal(8); | ||||
|     this.accelerationFlowCompleted = false; | ||||
|     this.showAccelerationSummary = true && this.acceleratorAvailable; | ||||
|     if (this.showAccelerationSummary) { | ||||
|       this.scrollIntoAccelPreview = true; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
| @ -753,6 +739,31 @@ export class TrackerComponent implements OnInit, OnDestroy { | ||||
|     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() { | ||||
|     this.error = undefined; | ||||
|     this.tx = null; | ||||
| @ -778,7 +789,7 @@ export class TrackerComponent implements OnInit, OnDestroy { | ||||
|     this.pool = null; | ||||
|     this.auditStatus = null; | ||||
|     this.accelerationPositions = null; | ||||
|     this.accelerationEligible = false; | ||||
|     this.eligibleForAcceleration = false; | ||||
|     this.trackerStage = 'waiting'; | ||||
|     document.body.scrollTo(0, 0); | ||||
|     this.leaveTransaction(); | ||||
|  | ||||
| @ -74,38 +74,9 @@ | ||||
| 
 | ||||
|   <ng-template [ngIf]="!isLoadingTx && !error"> | ||||
| 
 | ||||
|     <!-- 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 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> | ||||
| 
 | ||||
|     <!-- CPFP Details --> | ||||
|     <ng-template [ngIf]="showCpfpDetails"> | ||||
|       <br> | ||||
| 
 | ||||
|       <h2 class="text-left">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="xs"></fa-icon></h2> | ||||
|       <div class="box cpfp-details"> | ||||
|         <table class="table table-fixed table-borderless table-striped"> | ||||
| @ -161,6 +132,35 @@ | ||||
|       </div> | ||||
|     </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> | ||||
| 
 | ||||
|     <ng-container *ngIf="rbfInfo"> | ||||
| @ -551,18 +551,18 @@ | ||||
|         <td> | ||||
|           <ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton"> | ||||
|             @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> | ||||
|                 @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> | ||||
|                 } | ||||
|               </span> | ||||
|             } @else if (network === 'liquid' || network === 'liquidtestnet') { | ||||
|               <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time> | ||||
|             } @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> | ||||
|                 @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> | ||||
|                 } | ||||
|               </span> | ||||
|  | ||||
| @ -24,8 +24,8 @@ import { SeoService } from '../../services/seo.service'; | ||||
| import { StorageService } from '../../services/storage.service'; | ||||
| import { seoDescriptionNetwork } from '../../shared/common.utils'; | ||||
| import { getTransactionFlags, getUnacceleratedFeeRate } from '../../shared/transaction.utils'; | ||||
| import { Filter, toFilters } from '../../shared/filters.utils'; | ||||
| import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition, SinglePoolStats } from '../../interfaces/node-api.interface'; | ||||
| import { Filter, TransactionFlags, toFilters } from '../../shared/filters.utils'; | ||||
| import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '../../interfaces/node-api.interface'; | ||||
| import { LiquidUnblinding } from './liquid-ublinding'; | ||||
| import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; | ||||
| import { PriceService } from '../../services/price.service'; | ||||
| @ -137,12 +137,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|   hasEffectiveFeeRate: boolean; | ||||
|   accelerateCtaType: 'alert' | 'button' = 'button'; | ||||
|   acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === ''; | ||||
|   showAccelerationSummary = false; | ||||
|   eligibleForAcceleration: boolean = false; | ||||
|   forceAccelerationSummary = false; | ||||
|   hideAccelerationSummary = false; | ||||
|   accelerationFlowCompleted = false; | ||||
|   showAccelerationDetails = false; | ||||
|   hasAccelerationDetails = false; | ||||
|   accelerationFlowCompleted = 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; | ||||
| 
 | ||||
|   @ViewChild('graphContainer') | ||||
| @ -173,11 +175,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     this.enterpriseService.page(); | ||||
| 
 | ||||
|     const urlParams = new URLSearchParams(window.location.search); | ||||
|     if (urlParams.get('cash_request_id')) { | ||||
|       this.showAccelerationSummary = true; | ||||
|     } | ||||
|     this.forceAccelerationSummary = !!urlParams.get('cash_request_id'); | ||||
| 
 | ||||
|     if (!this.stateService.isLiquid) { | ||||
|     this.hideAccelerationSummary = this.storageService.getValue('hide-accelerator-pref') == 'true'; | ||||
| 
 | ||||
|     if (!this.stateService.isLiquid()) { | ||||
|       this.miningService.getMiningStats('1w').subscribe(stats => { | ||||
|         this.miningStats = stats; | ||||
|       }); | ||||
| @ -414,18 +416,17 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|           if (this.stateService.network === '') { | ||||
|             if (!this.mempoolPosition.accelerated) { | ||||
|               if (!this.accelerationFlowCompleted && !this.showAccelerationSummary) { | ||||
|                 this.showAccelerationSummary = true; | ||||
|               if (!this.accelerationFlowCompleted && !this.hideAccelerationSummary && !this.showAccelerationSummary) { | ||||
|                 this.miningService.getMiningStats('1w').subscribe(stats => { | ||||
|                   this.miningStats = stats; | ||||
|                 }); | ||||
|               } | ||||
|               if (txPosition.position?.block > 0 && this.tx.weight < 4000) { | ||||
|                 this.accelerationEligible = true; | ||||
|                 this.cashappEligible = true; | ||||
|               } | ||||
|             } else if (this.showAccelerationSummary) { | ||||
|               setTimeout(() => { | ||||
|                 this.closeAccelerator(); | ||||
|                 this.accelerationFlowCompleted = true; | ||||
|               }, 2000); | ||||
|             } | ||||
|           } | ||||
| @ -715,8 +716,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|     document.location.hash = '#accelerate'; | ||||
|     this.enterpriseService.goal(8); | ||||
|     this.accelerationFlowCompleted = false; | ||||
|     this.showAccelerationSummary = this.acceleratorAvailable; | ||||
|     this.openAccelerator(); | ||||
|     this.scrollIntoAccelPreview = true; | ||||
|     return false; | ||||
|   } | ||||
| @ -778,7 +778,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     } | ||||
| 
 | ||||
|     if (!this.isAcceleration && this.fragmentParams.has('accelerate')) { | ||||
|       this.onAccelerateClicked(); | ||||
|       this.forceAccelerationSummary = true; | ||||
|     } | ||||
| 
 | ||||
|     this.txChanged$.next(true); | ||||
| @ -797,10 +797,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     if (this.isAcceleration) { | ||||
|       if (initialState) { | ||||
|         this.accelerationFlowCompleted = true; | ||||
|         this.showAccelerationSummary = false; | ||||
|       } else if (this.showAccelerationSummary) { | ||||
|         setTimeout(() => { | ||||
|           this.closeAccelerator(); | ||||
|           this.accelerationFlowCompleted = true; | ||||
|         }, 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.tx.flags = getTransactionFlags(this.tx); | ||||
|       this.filters = this.tx.flags ? toFilters(this.tx.flags).filter(f => f.txPage) : []; | ||||
|       this.checkAccelerationEligibility(); | ||||
|     } else { | ||||
|       this.segwitEnabled = false; | ||||
|       this.taprootEnabled = false; | ||||
| @ -829,6 +829,16 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     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 { | ||||
|     if (!this.auditEnabled) { | ||||
|       return false; | ||||
| @ -873,7 +883,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     this.showCpfpDetails = false; | ||||
|     this.showAccelerationDetails = false; | ||||
|     this.accelerationInfo = null; | ||||
|     this.accelerationEligible = false; | ||||
|     this.cashappEligible = false; | ||||
|     this.txInBlockIndex = null; | ||||
|     this.mempoolPosition = null; | ||||
|     this.pool = null; | ||||
| @ -882,6 +892,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     document.body.scrollTo(0, 0); | ||||
|     this.isAcceleration = false; | ||||
|     this.isAccelerated$.next(this.isAcceleration); | ||||
|     this.eligibleForAcceleration = false; | ||||
|     this.leaveTransaction(); | ||||
|   } | ||||
| 
 | ||||
| @ -890,11 +901,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     this.stateService.markBlock$.next({}); | ||||
|   } | ||||
| 
 | ||||
|   closeAccelerator(): void { | ||||
|     this.accelerationFlowCompleted = true; | ||||
|     this.showAccelerationSummary = false; | ||||
|   } | ||||
| 
 | ||||
|   roundToOneDecimal(cpfpTx: any): number { | ||||
|     return +(cpfpTx.fee / (cpfpTx.weight / 4)).toFixed(1); | ||||
|   } | ||||
| @ -963,6 +969,32 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|     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() { | ||||
|     this.subscription.unsubscribe(); | ||||
|     this.fetchCpfpSubscription.unsubscribe(); | ||||
|  | ||||
| @ -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 | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user