Merge branch 'master' into mononaut/tracker-tx-routing
This commit is contained in:
		
						commit
						adea897e93
					
				| @ -165,6 +165,7 @@ class BitcoinRoutes { | ||||
|           acceleration: tx.acceleration, | ||||
|           acceleratedBy: tx.acceleratedBy || undefined, | ||||
|           acceleratedAt: tx.acceleratedAt || undefined, | ||||
|           feeDelta: tx.feeDelta || undefined, | ||||
|         }); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
| @ -453,6 +453,7 @@ class MempoolBlocks { | ||||
|             mempoolTx.acceleration = true; | ||||
|             mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools; | ||||
|             mempoolTx.acceleratedAt = acceleration?.added; | ||||
|             mempoolTx.feeDelta = acceleration?.feeDelta; | ||||
|             for (const ancestor of mempoolTx.ancestors || []) { | ||||
|               if (!mempool[ancestor.txid].acceleration) { | ||||
|                 mempool[ancestor.txid].cpfpDirty = true; | ||||
| @ -460,6 +461,7 @@ class MempoolBlocks { | ||||
|               mempool[ancestor.txid].acceleration = true; | ||||
|               mempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy; | ||||
|               mempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt; | ||||
|               mempool[ancestor.txid].feeDelta = mempoolTx.feeDelta; | ||||
|               isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy; | ||||
|             } | ||||
|           } else { | ||||
|  | ||||
| @ -823,6 +823,7 @@ class WebsocketHandler { | ||||
|               accelerated: mempoolTx.acceleration || undefined, | ||||
|               acceleratedBy: mempoolTx.acceleratedBy || undefined, | ||||
|               acceleratedAt: mempoolTx.acceleratedAt || undefined, | ||||
|               feeDelta: mempoolTx.feeDelta || undefined, | ||||
|             }, | ||||
|             accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid), | ||||
|           }; | ||||
| @ -864,6 +865,7 @@ class WebsocketHandler { | ||||
|               accelerated: mempoolTx.acceleration || undefined, | ||||
|               acceleratedBy: mempoolTx.acceleratedBy || undefined, | ||||
|               acceleratedAt: mempoolTx.acceleratedAt || undefined, | ||||
|               feeDelta: mempoolTx.feeDelta || undefined, | ||||
|             }; | ||||
|             if (!mempoolTx.cpfpChecked) { | ||||
|               calculateMempoolTxCpfp(mempoolTx, newMempool); | ||||
| @ -1138,6 +1140,7 @@ class WebsocketHandler { | ||||
|                 accelerated: mempoolTx.acceleration || undefined, | ||||
|                 acceleratedBy: mempoolTx.acceleratedBy || undefined, | ||||
|                 acceleratedAt: mempoolTx.acceleratedAt || undefined, | ||||
|                 feeDelta: mempoolTx.feeDelta || undefined, | ||||
|               }, | ||||
|               accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid), | ||||
|             }); | ||||
| @ -1160,6 +1163,7 @@ class WebsocketHandler { | ||||
|                 accelerated: mempoolTx.acceleration || undefined, | ||||
|                 acceleratedBy: mempoolTx.acceleratedBy || undefined, | ||||
|                 acceleratedAt: mempoolTx.acceleratedAt || undefined, | ||||
|                 feeDelta: mempoolTx.feeDelta || undefined, | ||||
|               }; | ||||
|             } | ||||
|           } | ||||
|  | ||||
| @ -126,6 +126,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction { | ||||
|   acceleration?: boolean; | ||||
|   acceleratedBy?: number[]; | ||||
|   acceleratedAt?: number; | ||||
|   feeDelta?: number; | ||||
|   replacement?: boolean; | ||||
|   uid?: number; | ||||
|   flags?: number; | ||||
| @ -449,7 +450,7 @@ export interface OptimizedStatistic { | ||||
| 
 | ||||
| export interface TxTrackingInfo { | ||||
|   replacedBy?: string, | ||||
|   position?: { block: number, vsize: number, accelerated?: boolean, acceleratedBy?: number[], acceleratedAt?: number }, | ||||
|   position?: { block: number, vsize: number, accelerated?: boolean, acceleratedBy?: number[], acceleratedAt?: number, feeDelta?: number }, | ||||
|   cpfp?: { | ||||
|     ancestors?: Ancestor[], | ||||
|     bestDescendant?: Ancestor | null, | ||||
| @ -462,6 +463,7 @@ export interface TxTrackingInfo { | ||||
|   accelerated?: boolean, | ||||
|   acceleratedBy?: number[], | ||||
|   acceleratedAt?: number, | ||||
|   feeDelta?: number, | ||||
|   confirmed?: boolean | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,62 @@ | ||||
| <div | ||||
|   #tooltip | ||||
|   *ngIf="accelerationInfo && tooltipPosition !== null" | ||||
|   class="acceleration-tooltip" | ||||
|   [style.left]="tooltipPosition.x + 'px'" | ||||
|   [style.top]="tooltipPosition.y + 'px'" | ||||
| > | ||||
|   <table> | ||||
|     <tbody> | ||||
|       <tr> | ||||
|         <td class="label" i18n="transaction.status|Transaction Status">Status</td> | ||||
|         <td class="value"> | ||||
|           @if (accelerationInfo.status === 'seen') { | ||||
|           <span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span> | ||||
|           } @else if (accelerationInfo.status === 'accelerated') { | ||||
|           <span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span> | ||||
|           } @else if (accelerationInfo.status === 'mined') { | ||||
|           <span class="badge badge-success" i18n="transaction.rbf.mined">Mined</span> | ||||
|           } | ||||
|         </td> | ||||
|       </tr> | ||||
|       <tr *ngIf="accelerationInfo.fee"> | ||||
|         <td class="label" i18n="transaction.fee|Transaction fee">Fee</td> | ||||
|         <td class="value">{{ accelerationInfo.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td> | ||||
|       </tr> | ||||
|       <tr *ngIf="accelerationInfo.bidBoost >= 0 || accelerationInfo.feeDelta"> | ||||
|         <td class="label" i18n="transaction.out-of-band-fees">Out-of-band fees</td> | ||||
|         @if (accelerationInfo.status === 'accelerated') { | ||||
|           <td style="color: #905cf4;" class="value">{{ accelerationInfo.feeDelta | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td> | ||||
|         } @else { | ||||
|           <td style="color: #905cf4;" class="value">{{ accelerationInfo.bidBoost | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td> | ||||
|         } | ||||
|       </tr> | ||||
|       <tr *ngIf="accelerationInfo.fee && accelerationInfo.weight && (accelerationInfo.feeDelta || accelerationInfo.bidBoost)"> | ||||
|         @if (accelerationInfo.status === 'seen') { | ||||
|           <td class="label" i18n="transaction.fee-rate">Fee rate</td> | ||||
|           <td class="value"><app-fee-rate [fee]="accelerationInfo.fee" [weight]="accelerationInfo.weight"></app-fee-rate></td> | ||||
|         } @else if (accelerationInfo.status === 'accelerated' || accelerationInfo.status === 'mined') { | ||||
|           <td class="label" i18n="transaction.accelerated-fee-rate|Accelerated transaction fee rate">Accelerated fee rate</td> | ||||
|           @if (accelerationInfo.status === 'accelerated') { | ||||
|             <td class="value"><app-fee-rate [fee]="accelerationInfo.fee + (accelerationInfo.feeDelta || 0)" [weight]="accelerationInfo.weight"></app-fee-rate></td> | ||||
|           } @else { | ||||
|             <td class="value"><app-fee-rate [fee]="accelerationInfo.fee + (accelerationInfo.bidBoost || 0)" [weight]="accelerationInfo.weight"></app-fee-rate></td> | ||||
|           } | ||||
|         } | ||||
|       </tr> | ||||
|       <tr *ngIf="['accelerated', 'mined'].includes(accelerationInfo.status) && hasPoolsData()"> | ||||
|         <td class="label" i18n="transaction.accelerated-by-hashrate|Accelerated to hashrate">Accelerated by</td> | ||||
|         <td class="value" *ngIf="accelerationInfo.pools"> | ||||
|           <ng-container *ngFor="let pool of accelerationInfo.pools"> | ||||
|             <img *ngIf="accelerationInfo.poolsData[pool]"  | ||||
|               class="pool-logo"  | ||||
|               [class.highlight]="pool === accelerationInfo?.minedByPoolUniqueId" | ||||
|               [src]="'/resources/mining-pools/' + accelerationInfo.poolsData[pool].slug + '.svg'"  | ||||
|               onError="this.src = '/resources/mining-pools/default.svg'"  | ||||
|               [alt]="'Logo of ' + pool.name + ' mining pool'"> | ||||
|           </ng-container> | ||||
|         </td> | ||||
|       </tr> | ||||
|     </tbody> | ||||
|   </table> | ||||
| </div> | ||||
| @ -0,0 +1,52 @@ | ||||
| .acceleration-tooltip { | ||||
|   position: fixed; | ||||
|   z-index: 3; | ||||
|   background: color-mix(in srgb, var(--active-bg) 95%, transparent); | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 1px 1px 10px rgba(0,0,0,0.5); | ||||
|   color: var(--tooltip-grey); | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: space-between; | ||||
|   padding: 10px 15px; | ||||
|   text-align: left; | ||||
|   pointer-events: none; | ||||
| 
 | ||||
|   .badge.badge-accelerated { | ||||
|     background-color: var(--tertiary); | ||||
|     color: white; | ||||
|   } | ||||
| 
 | ||||
|   .value { | ||||
|     text-align: end; | ||||
|   } | ||||
| 
 | ||||
|   .label { | ||||
|     padding-right: 30px; | ||||
|   } | ||||
| 
 | ||||
|   .pool-logo { | ||||
|     width: 22px; | ||||
|     height: 22px; | ||||
|     position: relative; | ||||
|     top: -1px; | ||||
|     margin-right: 3px; | ||||
|   } | ||||
|    | ||||
|   .highlight { | ||||
|     filter: drop-shadow(0 0 5px #905cf4); | ||||
|     animation: pulse 1s infinite; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @keyframes pulse { | ||||
|   0% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
|   50% { | ||||
|     transform: scale(1.2); | ||||
|   } | ||||
|   100% { | ||||
|     transform: scale(1); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,38 @@ | ||||
| import { Component, ElementRef, ViewChild, Input, OnChanges } from '@angular/core'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-acceleration-timeline-tooltip', | ||||
|   templateUrl: './acceleration-timeline-tooltip.component.html', | ||||
|   styleUrls: ['./acceleration-timeline-tooltip.component.scss'], | ||||
| }) | ||||
| export class AccelerationTimelineTooltipComponent implements OnChanges { | ||||
|   @Input() accelerationInfo: any; | ||||
|   @Input() cursorPosition: { x: number, y: number }; | ||||
| 
 | ||||
|   tooltipPosition: any = null; | ||||
| 
 | ||||
|   @ViewChild('tooltip') tooltipElement: ElementRef<HTMLCanvasElement>; | ||||
| 
 | ||||
|   constructor() {} | ||||
| 
 | ||||
|   ngOnChanges(changes): void { | ||||
|     if (changes.cursorPosition && changes.cursorPosition.currentValue) { | ||||
|       let x = Math.max(10, changes.cursorPosition.currentValue.x - 50); | ||||
|       let y = changes.cursorPosition.currentValue.y + 20; | ||||
|       if (this.tooltipElement) { | ||||
|         const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect(); | ||||
|         if ((x + elementBounds.width) > (window.innerWidth - 10)) { | ||||
|           x = Math.max(0, window.innerWidth - elementBounds.width - 10); | ||||
|         } | ||||
|         if (y + elementBounds.height > (window.innerHeight - 20)) { | ||||
|           y = y - elementBounds.height - 20; | ||||
|         } | ||||
|       } | ||||
|       this.tooltipPosition = { x, y }; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   hasPoolsData(): boolean { | ||||
|     return Object.keys(this.accelerationInfo.poolsData).length > 0; | ||||
|   } | ||||
| } | ||||
| @ -26,7 +26,7 @@ | ||||
|         <div class="node" [id]="'confirmed'"> | ||||
|           <div class="acc-to-confirmed left go-faster"></div> | ||||
|           <div class="shape-border waiting"> | ||||
|             <div class="shape animate"></div> | ||||
|             <div class="shape"></div> | ||||
|           </div> | ||||
|           <div class="status"><span class="badge badge-waiting" i18n="transaction.rbf.mined">Mined</span></div> | ||||
|         </div> | ||||
| @ -58,7 +58,7 @@ | ||||
|       <div class="nodes"> | ||||
|         <div class="node" [id]="'first-seen'"> | ||||
|           <div class="seen-to-acc right"></div> | ||||
|           <div class="shape-border"> | ||||
|           <div class="shape-border hovering" (pointerover)="onHover($event, 'seen');" (pointerout)="onBlur($event);"> | ||||
|             <div class="shape"></div> | ||||
|           </div> | ||||
|           <div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div> | ||||
| @ -80,7 +80,7 @@ | ||||
|           } @else { | ||||
|           <div class="seen-to-acc right"></div> | ||||
|           } | ||||
|           <div  class="shape-border"> | ||||
|           <div  class="shape-border hovering" (pointerover)="onHover($event, 'accelerated');" (pointerout)="onBlur($event);"> | ||||
|             <div class="shape"></div> | ||||
|             @if (!tx.status.confirmed) { | ||||
|             <div class="connector down loading"></div> | ||||
| @ -113,7 +113,10 @@ | ||||
|           } @else { | ||||
|           <div class="seen-to-acc left"></div> | ||||
|           } | ||||
|           <div class="shape-border" [class.waiting]="!tx.status.confirmed"> | ||||
|           <div class="shape-border"  | ||||
|               [ngClass]="{'waiting': !tx.status.confirmed, 'hovering': tx.status.confirmed}" | ||||
|               (pointerover)="onHover($event, tx.status.confirmed ? 'mined' : null)"  | ||||
|               (pointerout)="onBlur($event);"> | ||||
|             <div class="shape"></div> | ||||
|           </div> | ||||
|           @if (tx.status.confirmed) { | ||||
| @ -130,4 +133,10 @@ | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
|   <app-acceleration-timeline-tooltip | ||||
|   [accelerationInfo]="hoverInfo" | ||||
|   [cursorPosition]="tooltipPosition" | ||||
| ></app-acceleration-timeline-tooltip> | ||||
| 
 | ||||
| </div> | ||||
|  | ||||
| @ -152,9 +152,16 @@ | ||||
|         margin-bottom: -8px; | ||||
|         transform: translateY(-50%); | ||||
|         border-radius: 50%; | ||||
|         cursor: pointer; | ||||
|         padding: 4px; | ||||
|         background: transparent; | ||||
|         transition: background-color 300ms, padding 300ms; | ||||
| 
 | ||||
|         &.hovering { | ||||
|           cursor: pointer; | ||||
|           &:hover { | ||||
|             padding: 0px; | ||||
|           } | ||||
|         } | ||||
|      | ||||
|         .shape { | ||||
|           position: relative; | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| import { Component, Input, OnInit, OnChanges } from '@angular/core'; | ||||
| import { Component, Input, OnInit, OnChanges, HostListener } from '@angular/core'; | ||||
| import { ETA } from '../../services/eta.service'; | ||||
| import { Transaction } from '../../interfaces/electrs.interface'; | ||||
| import { Acceleration, SinglePoolStats } from '../../interfaces/node-api.interface'; | ||||
| import { MiningService } from '../../services/mining.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-acceleration-timeline', | ||||
| @ -10,6 +12,7 @@ import { Transaction } from '../../interfaces/electrs.interface'; | ||||
| export class AccelerationTimelineComponent implements OnInit, OnChanges { | ||||
|   @Input() transactionTime: number; | ||||
|   @Input() tx: Transaction; | ||||
|   @Input() accelerationInfo: Acceleration; | ||||
|   @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)
 | ||||
| @ -22,13 +25,25 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges { | ||||
|   useAbsoluteTime: boolean = false; | ||||
|   interval: number; | ||||
| 
 | ||||
|   constructor() {} | ||||
|   tooltipPosition = null; | ||||
|   hoverInfo: any = null; | ||||
|   poolsData: { [id: number]: SinglePoolStats } = {}; | ||||
| 
 | ||||
|   constructor( | ||||
|     private miningService: MiningService, | ||||
|   ) {} | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|     this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000; | ||||
|     this.now = Math.floor(new Date().getTime() / 1000); | ||||
|     this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600; | ||||
| 
 | ||||
|     this.miningService.getPools().subscribe(pools => { | ||||
|       for (const pool of pools) { | ||||
|         this.poolsData[pool.unique_id] = pool; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     this.interval = window.setInterval(() => { | ||||
|       this.now = Math.floor(new Date().getTime() / 1000); | ||||
|       this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600; | ||||
| @ -52,4 +67,42 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges { | ||||
|   ngOnDestroy(): void { | ||||
|     clearInterval(this.interval); | ||||
|   } | ||||
|    | ||||
|   onHover(event, status: string): void { | ||||
|     if (status === 'seen') { | ||||
|       this.hoverInfo = { | ||||
|         status, | ||||
|         fee: this.tx.fee, | ||||
|         weight: this.tx.weight | ||||
|       }; | ||||
|     } else if (status === 'accelerated') { | ||||
|       this.hoverInfo = { | ||||
|         status, | ||||
|         fee: this.accelerationInfo?.effectiveFee || this.tx.fee, | ||||
|         weight: this.tx.weight, | ||||
|         feeDelta: this.accelerationInfo?.feeDelta || this.tx.feeDelta, | ||||
|         pools: this.tx.acceleratedBy || this.accelerationInfo?.pools, | ||||
|         poolsData: this.poolsData | ||||
|       }; | ||||
|     } else if (status === 'mined') { | ||||
|       this.hoverInfo = { | ||||
|         status, | ||||
|         fee: this.accelerationInfo?.effectiveFee, | ||||
|         weight: this.tx.weight, | ||||
|         bidBoost: this.accelerationInfo?.bidBoost, | ||||
|         minedByPoolUniqueId: this.accelerationInfo?.minedByPoolUniqueId, | ||||
|         pools: this.tx.acceleratedBy || this.accelerationInfo?.pools, | ||||
|         poolsData: this.poolsData | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   onBlur(event): void { | ||||
|     this.hoverInfo = null; | ||||
|   } | ||||
| 
 | ||||
|   @HostListener('pointermove', ['$event']) | ||||
|   onPointerMove(event) { | ||||
|     this.tooltipPosition = { x: event.clientX, y: event.clientY }; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -167,7 +167,7 @@ | ||||
|         <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> | ||||
|       <app-acceleration-timeline [transactionTime]="transactionTime" [tx]="tx" [accelerationInfo]="accelerationInfo" [eta]="(ETA$ | async)" [standardETA]="(standardETA$ | async)?.time"></app-acceleration-timeline> | ||||
|       <br> | ||||
|     </ng-container> | ||||
| 
 | ||||
|  | ||||
| @ -823,6 +823,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|       this.tx.acceleration = cpfpInfo.acceleration; | ||||
|       this.tx.acceleratedBy = cpfpInfo.acceleratedBy; | ||||
|       this.tx.acceleratedAt = cpfpInfo.acceleratedAt; | ||||
|       this.tx.feeDelta = cpfpInfo.feeDelta; | ||||
|       this.setIsAccelerated(firstCpfp); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -22,6 +22,7 @@ export interface Transaction { | ||||
|   acceleration?: boolean; | ||||
|   acceleratedBy?: number[]; | ||||
|   acceleratedAt?: number; | ||||
|   feeDelta?: number; | ||||
|   deleteAfter?: number; | ||||
|   _unblinded?: any; | ||||
|   _deduced?: boolean; | ||||
|  | ||||
| @ -31,6 +31,7 @@ export interface CpfpInfo { | ||||
|   acceleration?: boolean; | ||||
|   acceleratedBy?: number[]; | ||||
|   acceleratedAt?: number; | ||||
|   feeDelta?: number; | ||||
| } | ||||
| 
 | ||||
| export interface RbfInfo { | ||||
|  | ||||
| @ -68,6 +68,7 @@ import { AddressTransactionsWidgetComponent } from '../components/address-transa | ||||
| import { RbfTimelineComponent } from '../components/rbf-timeline/rbf-timeline.component'; | ||||
| import { AccelerationTimelineComponent } from '../components/acceleration-timeline/acceleration-timeline.component'; | ||||
| import { RbfTimelineTooltipComponent } from '../components/rbf-timeline/rbf-timeline-tooltip.component'; | ||||
| import { AccelerationTimelineTooltipComponent } from '../components/acceleration-timeline/acceleration-timeline-tooltip.component'; | ||||
| import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component'; | ||||
| import { TestTransactionsComponent } from '../components/test-transactions/test-transactions.component'; | ||||
| import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component'; | ||||
| @ -180,6 +181,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | ||||
|     RbfTimelineComponent, | ||||
|     AccelerationTimelineComponent, | ||||
|     RbfTimelineTooltipComponent, | ||||
|     AccelerationTimelineTooltipComponent, | ||||
|     PushTransactionComponent, | ||||
|     TestTransactionsComponent, | ||||
|     AssetsNavComponent, | ||||
| @ -320,6 +322,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir | ||||
|     RbfTimelineComponent, | ||||
|     AccelerationTimelineComponent, | ||||
|     RbfTimelineTooltipComponent, | ||||
|     AccelerationTimelineTooltipComponent, | ||||
|     PushTransactionComponent, | ||||
|     TestTransactionsComponent, | ||||
|     AssetsNavComponent, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user