Merge branch 'master' into simon/block-mining-pool-logos
This commit is contained in:
		
						commit
						bf95938be8
					
				| @ -1,3 +1,4 @@ | ||||
| @if (tx.status.confirmed) { | ||||
| <div class="acceleration-timeline box"> | ||||
|   <div class="timeline-wrapper"> | ||||
|     <div class="timeline"> | ||||
| @ -11,68 +12,141 @@ | ||||
|         <div class="node-spacer"></div> | ||||
|         <div class="interval"> | ||||
|           <div class="interval-time"> | ||||
|             @if (eta) { | ||||
|             ~<app-time kind="plain" [time]="eta?.wait / 1000"></app-time> | ||||
|             } @else if (tx.status.block_time) { | ||||
|             <app-time kind="plain" [time]="tx.status.block_time - acceleratedAt"></app-time> | ||||
|             } | ||||
|             <app-time [time]="tx.status.block_time - acceleratedAt"></app-time> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="node-spacer"></div> | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
|     <div class="nodes"> | ||||
|       <div class="node" [id]="'first-seen'"> | ||||
|         <div class="seen-to-acc right" [class.loading]="!tx.acceleration && !tx.status.confirmed"></div> | ||||
|         <a class="shape-border" [class.sent-selected]="!tx.status.confirmed && !tx.acceleration"> | ||||
|           <div class="shape"></div> | ||||
|         </a> | ||||
|         <div class="status"><span class="badge badge-primary">Sent</span></div> | ||||
|         <div class="time"> | ||||
|           <app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time> | ||||
|       <div class="nodes"> | ||||
|         <div class="node" [id]="'first-seen'"> | ||||
|           <div class="seen-to-acc right"></div> | ||||
|           <div class="shape-border"> | ||||
|             <div class="shape"></div> | ||||
|           </div> | ||||
|           <div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div> | ||||
|           <div class="time"> | ||||
|             <app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="interval-spacer"> | ||||
|         <div class="seen-to-acc" [class.loading]="!tx.acceleration && !tx.status.confirmed"></div> | ||||
|       </div> | ||||
|       <div class="node" [id]="'accelerated'"> | ||||
|         <div class="seen-to-acc left" [class.loading]="!tx.acceleration && !tx.status.confirmed"></div> | ||||
|         <div class="acc-to-confirmed right" [class.loading]="tx.acceleration && !tx.status.confirmed"></div> | ||||
|         <a class="shape-border" [class.accelerated-selected]="tx.acceleration && !tx.status.confirmed"> | ||||
|           <div class="shape"></div> | ||||
|         </a> | ||||
|         <div class="status" [style]="!tx.acceleration && !tx.status.confirmed ? 'opacity: 0.5' : ''"><span class="badge badge-accelerated">Accelerated</span></div> | ||||
|         <div class="time"> | ||||
|           <app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time> | ||||
|         <div class="interval-spacer"> | ||||
|           <div class="seen-to-acc"></div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="interval-spacer"> | ||||
|         <div class="acc-to-confirmed" [class.loading]="tx.acceleration && !tx.status.confirmed"></div> | ||||
|       </div> | ||||
|       <div class="node" [id]="'confirmed'" [class.mined]="tx.status.confirmed"> | ||||
|         <div class="acc-to-confirmed left" [class.loading]="tx.acceleration && !tx.status.confirmed"></div> | ||||
|         <a class="shape-border" [class.mined-selected]="tx.status.confirmed"> | ||||
|           <div class="shape"></div> | ||||
|         </a> | ||||
|         <div class="status" [style]="!tx.status.confirmed ? 'opacity: 0.5' : ''"><span class="badge badge-success">Mined</span></div> | ||||
|         <div class="time"> | ||||
|           @if (tx.status.block_time) { | ||||
|         <div class="node" [id]="'accelerated'"> | ||||
|           <div class="seen-to-acc left"></div> | ||||
|           <div class="acc-to-confirmed right"></div> | ||||
|           <div class="shape-border"> | ||||
|             <div class="shape"></div> | ||||
|           </div> | ||||
|           <div class="status"><span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span></div> | ||||
|           <div class="time"> | ||||
|             <app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="interval-spacer"> | ||||
|           <div class="acc-to-confirmed"></div> | ||||
|         </div> | ||||
|         <div class="node mined" [id]="'confirmed'" > | ||||
|           <div class="acc-to-confirmed left" ></div> | ||||
|           <div class="shape-border mined-selected"> | ||||
|             <div class="shape"></div> | ||||
|           </div> | ||||
|           <div class="status"><span class="badge badge-success" i18n="transaction.rbf.mined">Mined</span></div> | ||||
|           <div class="time"> | ||||
|             <app-time kind="since" [time]="tx.status.block_time"></app-time> | ||||
|           } @else if (eta) { | ||||
|             <app-time kind="until" [time]="eta?.time"></app-time> | ||||
|           } | ||||
|           </div> | ||||
|         </div> | ||||
|       </div>   | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| } @else if (acceleratedETA) { <!-- Not yet accelerated; to be shown only in acceleration checkout --> | ||||
| } @else if (standardETA) { <!-- Accelerated --> | ||||
|   <div class="acceleration-timeline box"> | ||||
|     <div class="timeline-wrapper"> | ||||
|       <div class="timeline"> | ||||
|         <div class="intervals"> | ||||
|           <div class="node-spacer"></div> | ||||
|           <div class="interval-spacer"></div> | ||||
|           <div class="node-spacer"></div> | ||||
|           <div class="interval"> | ||||
|             <div class="interval-time"> | ||||
|               @if (eta) { | ||||
|                 ~<app-time [time]="eta?.wait / 1000"></app-time> <span *ngIf="accelerateRatio > 1" class="compare"> ({{ accelerateRatio }}x faster)</span> | ||||
|                 } | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="node-spacer"></div> | ||||
|         </div> | ||||
|         <div class="nodes"> | ||||
|           <div class="node-spacer"></div> | ||||
|           <div class="interval-spacer"></div> | ||||
|           <div class="node"> | ||||
|             <div class="acc-to-confirmed loading right"></div> | ||||
|           </div> | ||||
|           <div class="interval-spacer"> | ||||
|             <div class="acc-to-confirmed loading"></div> | ||||
|           </div> | ||||
|           <div class="node" [id]="'confirmed'"> | ||||
|             <div class="acc-to-confirmed loading left"></div> | ||||
|             <div class="shape-border waiting"> | ||||
|               <div class="shape animate"></div> | ||||
|             </div> | ||||
|             <div class="status"><span class="badge badge-waiting" i18n="transaction.rbf.mined">Mined</span></div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="timeline"> | ||||
|         <div class="intervals"> | ||||
|           <div class="node-spacer"></div> | ||||
|           <div class="interval"> | ||||
|             <div class="interval-time"> | ||||
|               <app-time [time]="acceleratedAt - transactionTime"></app-time> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="node-spacer"></div> | ||||
|           <div class="interval"> | ||||
|             <div class="interval-time"> | ||||
|                 ~<app-time [time]="standardETA / 1000 - now"></app-time> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="node-spacer"></div> | ||||
|         </div> | ||||
|         <div class="nodes"> | ||||
|           <div class="node" [id]="'first-seen'"> | ||||
|             <div class="seen-to-acc right"></div> | ||||
|             <div class="shape-border"> | ||||
|               <div class="shape"></div> | ||||
|             </div> | ||||
|             <div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div> | ||||
|             <div class="time"> | ||||
|               <app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="interval-spacer"> | ||||
|             <div class="seen-to-acc"></div> | ||||
|           </div> | ||||
|           <div class="node" [id]="'accelerated'"> | ||||
|             <div class="seen-to-acc left"></div> | ||||
|             <div class="seen-to-acc right"></div> | ||||
|             <div  class="shape-border accelerated-selected"> | ||||
|               <div class="shape accelerating"></div> | ||||
|               <div class="connector down loading"></div> | ||||
|             </div> | ||||
|             <div class="time" style="margin-top: 3px;"> | ||||
|               <span i18n="transaction.audit.accelerated">Accelerated</span> <app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="interval-spacer"> | ||||
|             <div class="seen-to-acc"></div> | ||||
|           </div> | ||||
|           <div class="node" [id]="'confirmed'"> | ||||
|             <div class="seen-to-acc left"></div> | ||||
|             <div class="shape-border waiting"> | ||||
|               <div class="shape"></div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
|   <ng-template #nodeSpacer> | ||||
|     <div class="node-spacer"></div> | ||||
|   </ng-template> | ||||
| 
 | ||||
|   <ng-template #intervalSpacer> | ||||
|     <div class="interval-spacer"></div> | ||||
|   </ng-template> | ||||
| 
 | ||||
| </div> | ||||
| } | ||||
| @ -1,7 +1,7 @@ | ||||
| .acceleration-timeline { | ||||
|   position: relative; | ||||
|   width: 100%; | ||||
|   padding: 1em 0; | ||||
|   padding: 0.5em 0 1em; | ||||
| 
 | ||||
|   &::after, &::before { | ||||
|     content: ''; | ||||
| @ -69,6 +69,15 @@ | ||||
|       font-size: 12px; | ||||
|       line-height: 16px; | ||||
|       white-space: nowrap; | ||||
| 
 | ||||
|       .compare { | ||||
|         font-style: italic; | ||||
|         color: var(--mainnet-alt); | ||||
|         font-weight: 600; | ||||
|         @media (max-width: 600px) { | ||||
|           display: none; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -84,10 +93,6 @@ | ||||
|       background: var(--primary); | ||||
|       border-radius: 5px; | ||||
| 
 | ||||
|       &.loading { | ||||
|         animation: standardPulse 1s infinite; | ||||
|       } | ||||
| 
 | ||||
|       &.left { | ||||
|         right: 50%; | ||||
|       } | ||||
| @ -118,7 +123,28 @@ | ||||
|         left: 50%; | ||||
|       } | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     .connector { | ||||
|       position: absolute; | ||||
|       height: 88px; | ||||
|       width: 10px; | ||||
|       left: -5px; | ||||
|       top: -73px; | ||||
|       transform: translateX(120%); | ||||
|       background: var(--tertiary); | ||||
| 
 | ||||
|       &.down { | ||||
|         border-top-left-radius: 10px;  | ||||
|       } | ||||
| 
 | ||||
|       &.up { | ||||
|         border-top-right-radius: 10px;  | ||||
|       } | ||||
| 
 | ||||
|       &.loading { | ||||
|         animation: acceleratePulse 1s infinite; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .nodes { | ||||
| @ -134,20 +160,20 @@ | ||||
|         transform: translateY(-50%); | ||||
|         border-radius: 50%; | ||||
|         padding: 2px; | ||||
|         background: transparent; | ||||
|         transition: background-color 300ms, padding 300ms; | ||||
| 
 | ||||
|         .shape { | ||||
|           width: 100%; | ||||
|           height: 100%; | ||||
|           border-radius: 50%; | ||||
|           background: white; | ||||
|           transition: background-color 300ms, border 300ms; | ||||
|           &.accelerating { | ||||
|             animation: acceleratePulse 1s infinite; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         &.sent-selected { | ||||
|         &.waiting { | ||||
|           .shape { | ||||
|             background: var(--primary); | ||||
|             background: var(--grey); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
| @ -166,6 +192,12 @@ | ||||
| 
 | ||||
|       .status { | ||||
|         margin-top: -64px; | ||||
| 
 | ||||
|         .badge.badge-waiting { | ||||
|           opacity: 0.5; | ||||
|           background-color: var(--grey); | ||||
|           color: white; | ||||
|         } | ||||
|          | ||||
|         .badge.badge-accelerated { | ||||
|           background-color: var(--tertiary); | ||||
| @ -188,10 +220,3 @@ | ||||
|   50% { background-color: var(--mainnet-alt) } | ||||
|   100% { background-color: var(--tertiary) } | ||||
| } | ||||
| 
 | ||||
| @keyframes standardPulse { | ||||
|   0% { background-color: var(--primary) } | ||||
|   50% { background-color: var(--secondary) } | ||||
|   100% { background-color: var(--primary) } | ||||
|    | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Component, Input, OnInit, OnChanges, Inject, LOCALE_ID } from '@angular/core'; | ||||
| import { Component, Input, OnInit, OnChanges } from '@angular/core'; | ||||
| import { ETA } from '../../services/eta.service'; | ||||
| import { Transaction } from '../../interfaces/electrs.interface'; | ||||
| 
 | ||||
| @ -11,23 +11,31 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges { | ||||
|   @Input() transactionTime: number; | ||||
|   @Input() tx: Transaction; | ||||
|   @Input() eta: ETA; | ||||
|   // A mined transaction has standard ETA and accelerated ETA undefined
 | ||||
|   // A transaction in mempool has either standardETA defined (if accelerated) or acceleratedETA defined (if not accelerated yet)
 | ||||
|   @Input() standardETA: number; | ||||
|   @Input() acceleratedETA: number; | ||||
| 
 | ||||
|   acceleratedAt: number; | ||||
|   dir: 'rtl' | 'ltr' = 'ltr'; | ||||
|   now: number; | ||||
|   accelerateRatio: number; | ||||
| 
 | ||||
|   constructor( | ||||
|     @Inject(LOCALE_ID) private locale: string, | ||||
|   ) { | ||||
|     if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) { | ||||
|       this.dir = 'rtl'; | ||||
|     } | ||||
|   } | ||||
|   constructor() {} | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|     this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000; | ||||
|   } | ||||
| 
 | ||||
|   ngOnChanges(changes): void { | ||||
|     this.now = Math.floor(new Date().getTime() / 1000); | ||||
|     if (changes?.eta?.currentValue || changes?.standardETA?.currentValue || changes?.acceleratedETA?.currentValue) { | ||||
|       if (changes?.eta?.currentValue) { | ||||
|         if (changes?.acceleratedETA?.currentValue) { | ||||
|           this.accelerateRatio = Math.floor((Math.floor(changes.eta.currentValue.time / 1000) - this.now) / (Math.floor(changes.acceleratedETA.currentValue / 1000) - this.now)); | ||||
|         } else if (changes?.standardETA?.currentValue) { | ||||
|           this.accelerateRatio = Math.floor((Math.floor(changes.standardETA.currentValue / 1000) - this.now) / (Math.floor(changes.eta.currentValue.time / 1000) - this.now)); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -153,15 +153,6 @@ | ||||
| 
 | ||||
|     <br> | ||||
| 
 | ||||
|     <ng-container *ngIf="transactionTime && (tx.acceleration || isAcceleration)"> | ||||
|       <div class="title float-left"> | ||||
|         <h2 id="acceleration-timeline" i18n="transaction.acceleration-timeline|Acceleration Timeline">Acceleration Timeline</h2> | ||||
|       </div> | ||||
|       <div class="clearfix"></div> | ||||
|       <app-acceleration-timeline [transactionTime]="transactionTime" [tx]="tx" [eta]="(ETA$ | async)"></app-acceleration-timeline> | ||||
|       <br> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <ng-container *ngIf="rbfInfo"> | ||||
|       <div class="title float-left"> | ||||
|         <h2 id="rbf" i18n="transaction.rbf-history|RBF Timeline">RBF Timeline</h2> | ||||
| @ -171,6 +162,15 @@ | ||||
|       <br> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <ng-container *ngIf="transactionTime && isAcceleration"> | ||||
|       <div class="title float-left"> | ||||
|         <h2 id="acceleration-timeline" i18n="transaction.acceleration-timeline|Acceleration Timeline">Acceleration Timeline</h2> | ||||
|       </div> | ||||
|       <div class="clearfix"></div> | ||||
|       <app-acceleration-timeline [transactionTime]="transactionTime" [tx]="tx" [eta]="(ETA$ | async)" [standardETA]="(standardETA$ | async)?.time"></app-acceleration-timeline> | ||||
|       <br> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <ng-container *ngIf="flowEnabled; else flowPlaceholder"> | ||||
|       <div class="title float-left"> | ||||
|         <h2 id="flow" i18n="transaction.flow|Transaction flow">Flow</h2> | ||||
|  | ||||
| @ -65,6 +65,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|   txId: string; | ||||
|   txInBlockIndex: number; | ||||
|   mempoolPosition: MempoolPosition; | ||||
|   gotInitialPosition = false; | ||||
|   accelerationPositions: AccelerationPosition[]; | ||||
|   isLoadingTx = true; | ||||
|   error: any = undefined; | ||||
| @ -112,6 +113,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|   txChanged$ = new BehaviorSubject<boolean>(false); // triggered whenever this.tx changes (long term, we should refactor to make this.tx an observable itself)
 | ||||
|   isAccelerated$ = new BehaviorSubject<boolean>(false); // refactor this to make isAccelerated an observable itself
 | ||||
|   ETA$: Observable<ETA | null>; | ||||
|   standardETA$: Observable<ETA | null>; | ||||
|   isCached: boolean = false; | ||||
|   now = Date.now(); | ||||
|   da$: Observable<DifficultyAdjustment>; | ||||
| @ -431,9 +433,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|               if (txPosition.position?.block > 0 && this.tx.weight < 4000) { | ||||
|                 this.cashappEligible = true; | ||||
|               } | ||||
|               if (!this.gotInitialPosition && txPosition.position?.block === 0 && txPosition.position?.vsize < 750_000) { | ||||
|                 this.accelerationFlowCompleted = true; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         this.gotInitialPosition = true; | ||||
|       } else { | ||||
|         this.mempoolPosition = null; | ||||
|         this.accelerationPositions = null; | ||||
| @ -809,6 +815,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|         this.miningStats = stats; | ||||
|         this.isAccelerated$.next(this.isAcceleration); // hack to trigger recalculation of ETA without adding another source observable
 | ||||
|       }); | ||||
|       if (!this.tx.status?.confirmed) { | ||||
|         this.standardETA$ = combineLatest([ | ||||
|           this.stateService.mempoolBlocks$.pipe(startWith(null)), | ||||
|           this.stateService.difficultyAdjustment$.pipe(startWith(null)), | ||||
|         ]).pipe( | ||||
|           map(([mempoolBlocks, da]) => { | ||||
|             return this.etaService.calculateUnacceleratedETA( | ||||
|               this.tx, | ||||
|               mempoolBlocks, | ||||
|               da, | ||||
|               this.cpfpInfo, | ||||
|             ); | ||||
|           }) | ||||
|         ) | ||||
|       } | ||||
|     } | ||||
|     this.isAccelerated$.next(this.isAcceleration); | ||||
|   } | ||||
| @ -864,6 +885,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|   resetTransaction() { | ||||
|     this.firstLoad = false; | ||||
|     this.gotInitialPosition = false; | ||||
|     this.error = undefined; | ||||
|     this.tx = null; | ||||
|     this.txChanged$.next(true); | ||||
|  | ||||
| @ -225,4 +225,58 @@ export class EtaService { | ||||
|         blocks: Math.ceil(eta / da.adjustedTimeAvg), | ||||
|       }; | ||||
|   } | ||||
| 
 | ||||
|   calculateUnacceleratedETA( | ||||
|     tx: Transaction, | ||||
|     mempoolBlocks: MempoolBlock[], | ||||
|     da: DifficultyAdjustment, | ||||
|     cpfpInfo: CpfpInfo | null, | ||||
|   ): ETA | null { | ||||
|     if (!tx || !mempoolBlocks) { | ||||
|       return null; | ||||
|     } | ||||
|     const now = Date.now(); | ||||
| 
 | ||||
|     // use known projected position, or fall back to feerate-based estimate
 | ||||
|     const mempoolPosition = this.mempoolPositionFromFees(this.getFeeRateFromCpfpInfo(tx, cpfpInfo), mempoolBlocks); | ||||
|     if (!mempoolPosition) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     // difficulty adjustment estimate is required to know avg block time on non-Liquid networks
 | ||||
|     if (!da) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const blocks = mempoolPosition.block + 1; | ||||
|     const wait = da.adjustedTimeAvg * (mempoolPosition.block + 1); | ||||
|     return { | ||||
|       now, | ||||
|       time: wait + now + da.timeOffset, | ||||
|       wait, | ||||
|       blocks, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   getFeeRateFromCpfpInfo(tx: Transaction, cpfpInfo: CpfpInfo | null): number { | ||||
|     if (!cpfpInfo) { | ||||
|       return tx.fee / (tx.weight / 4); | ||||
|     } | ||||
| 
 | ||||
|     const relatives = [...(cpfpInfo.ancestors || []), ...(cpfpInfo.descendants || [])]; | ||||
|     if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) { | ||||
|       relatives.push(cpfpInfo.bestDescendant); | ||||
|     } | ||||
| 
 | ||||
|     if (!!relatives.length) { | ||||
|       const totalWeight = tx.weight + relatives.reduce((prev, val) => prev + val.weight, 0); | ||||
|       const totalFees = tx.fee + relatives.reduce((prev, val) => prev + val.fee, 0); | ||||
| 
 | ||||
|       return totalFees / (totalWeight / 4); | ||||
|     } | ||||
| 
 | ||||
|     return tx.fee / (tx.weight / 4); | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user