Add interactivity to tx sankey diagram
This commit is contained in:
		
							parent
							
								
									1e5cef4a62
								
							
						
					
					
						commit
						64f3a597a2
					
				| @ -196,7 +196,7 @@ | ||||
| 
 | ||||
|     <div class="box"> | ||||
|       <div class="graph-container" #graphContainer> | ||||
|         <tx-bowtie-graph [tx]="tx" [width]="graphWidth" [height]="graphExpanded ? (maxInOut * 15) : 360" [maxStrands]="graphExpanded ? maxInOut : 24" [network]="network"></tx-bowtie-graph> | ||||
|         <tx-bowtie-graph [tx]="tx" [width]="graphWidth" [height]="graphExpanded ? (maxInOut * 15) : graphHeight" [maxStrands]="graphExpanded ? maxInOut : 24" [network]="network" [tooltip]="true"></tx-bowtie-graph> | ||||
|       </div> | ||||
|       <div class="toggle-wrapper" *ngIf="maxInOut > 24"> | ||||
|         <button class="btn btn-sm btn-primary graph-toggle" (click)="expandGraph();" *ngIf="!graphExpanded; else collapseBtn"><span i18n="show-more">Show more</span></button> | ||||
|  | ||||
| @ -49,7 +49,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|   outputIndex: number; | ||||
|   graphExpanded: boolean = false; | ||||
|   graphWidth: number = 1000; | ||||
|   graphHeight: number = 360; | ||||
|   maxInOut: number = 0; | ||||
|   tooltipPosition: { x: number, y: number }; | ||||
| 
 | ||||
|   @ViewChild('graphContainer') | ||||
|   graphContainer: ElementRef; | ||||
| @ -296,7 +298,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|   } | ||||
| 
 | ||||
|   setupGraph() { | ||||
|     this.maxInOut = Math.min(250, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length || 1)); | ||||
|     this.maxInOut = Math.min(250, Math.max(this.tx?.vin?.length || 1, this.tx?.vout?.length + 1 || 1)); | ||||
|     this.graphHeight = Math.min(360, this.maxInOut * 80); | ||||
|   } | ||||
| 
 | ||||
|   expandGraph() { | ||||
| @ -309,7 +312,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|   @HostListener('window:resize', ['$event']) | ||||
|   setGraphSize(): void { | ||||
|     console.log('resize', this.graphContainer); | ||||
|     if (this.graphContainer) { | ||||
|       this.graphWidth = this.graphContainer.nativeElement.clientWidth - 24; | ||||
|     } | ||||
|  | ||||
| @ -0,0 +1,33 @@ | ||||
| <div | ||||
|   #tooltip | ||||
|   *ngIf="line" | ||||
|   class="bowtie-graph-tooltip" | ||||
|   [style.visibility]="line ? 'visible' : 'hidden'" | ||||
|   [style.left]="tooltipPosition.x + 'px'" | ||||
|   [style.top]="tooltipPosition.y + 'px'" | ||||
| > | ||||
|   <ng-container *ngIf="!line.rest; else restMsg"> | ||||
|     <p> | ||||
|       <ng-container [ngSwitch]="line.type"> | ||||
|         <span *ngSwitchCase="'input'" i18n="transaction.input">Input</span> | ||||
|         <span *ngSwitchCase="'output'" i18n="transaction.output">Output</span> | ||||
|         <span *ngSwitchCase="'fee'" i18n="transaction.fee">Fee</span> | ||||
|       </ng-container> | ||||
|       <span *ngIf="line.type !== 'fee'"> #{{ line.index }}</span> | ||||
|     </p> | ||||
|     <p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p> | ||||
|   </ng-container> | ||||
| 
 | ||||
|   <ng-template #restMsg> | ||||
|     <span>{{ line.rest }} </span> | ||||
|     <ng-container [ngSwitch]="line.type"> | ||||
|       <span *ngSwitchCase="'input'" i18n="transaction.other-inputs">other inputs</span> | ||||
|       <span *ngSwitchCase="'output'" i18n="transaction.other-outputs">other outputs</span> | ||||
|     </ng-container> | ||||
|   </ng-template> | ||||
| 
 | ||||
|   <p *ngIf="line.type !== 'fee' && line.address" class="address"> | ||||
|     <span class="first">{{ line.address.slice(0, -4) }}</span> | ||||
|     <span class="last-four">{{ line.address.slice(-4) }}</span> | ||||
|   </p> | ||||
| </div> | ||||
| @ -0,0 +1,38 @@ | ||||
| .bowtie-graph-tooltip { | ||||
|   position: absolute; | ||||
|   background: rgba(#11131f, 0.95); | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 1px 1px 10px rgba(0,0,0,0.5); | ||||
|   color: #b1b1b1; | ||||
|   padding: 10px 15px; | ||||
|   text-align: left; | ||||
|   pointer-events: none; | ||||
|   max-width: 300px; | ||||
| 
 | ||||
|   p { | ||||
|     margin: 0; | ||||
|     white-space: nowrap; | ||||
|   } | ||||
| 
 | ||||
|   .address { | ||||
|     width: 100%; | ||||
|     max-width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     align-items: baseline; | ||||
|     justify-content: flex-start; | ||||
| 
 | ||||
|     .first { | ||||
|       flex-grow: 0; | ||||
|       flex-shrink: 1; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|       margin-right: -2px; | ||||
|     } | ||||
| 
 | ||||
|     .last-four { | ||||
|       flex-shrink: 0; | ||||
|       flex-grow: 0; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core'; | ||||
| import { TransactionStripped } from 'src/app/interfaces/websocket.interface'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-tx-bowtie-graph-tooltip', | ||||
|   templateUrl: './tx-bowtie-graph-tooltip.component.html', | ||||
|   styleUrls: ['./tx-bowtie-graph-tooltip.component.scss'], | ||||
| }) | ||||
| export class TxBowtieGraphTooltipComponent implements OnChanges { | ||||
|   @Input() line: { type: string, value?: number, index?: number, address?: string, rest?: number } | void; | ||||
|   @Input() cursorPosition: { x: number, y: number }; | ||||
| 
 | ||||
|   tooltipPosition = { x: 0, y: 0 }; | ||||
| 
 | ||||
|   @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(); | ||||
|         const parentBounds = this.tooltipElement.nativeElement.offsetParent.getBoundingClientRect(); | ||||
|         if ((parentBounds.left + x + elementBounds.width) > parentBounds.right) { | ||||
|           x = Math.max(0, parentBounds.width - elementBounds.width - 10); | ||||
|         } | ||||
|         if (y + elementBounds.height > parentBounds.height) { | ||||
|           y = y - elementBounds.height - 20; | ||||
|         } | ||||
|       } | ||||
|       this.tooltipPosition = { x, y }; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,44 +1,82 @@ | ||||
| <svg *ngIf="inputs && outputs" class="bowtie" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'"> | ||||
|   <defs> | ||||
|     <marker id="input-arrow" viewBox="-5 -5 10 10" | ||||
|         refX="0" refY="0" | ||||
|         markerUnits="strokeWidth" | ||||
|         markerWidth="1.5" markerHeight="1" | ||||
|         orient="auto"> | ||||
|       <path d="M -5 -5 L 0 0 L -5 5 L 1 5 L 1 -5 Z" stroke-width="0" [attr.fill]="gradient[0]"/> | ||||
|     </marker> | ||||
|     <marker id="output-arrow" viewBox="-5 -5 10 10" | ||||
|         refX="0" refY="0" | ||||
|         markerUnits="strokeWidth" | ||||
|         markerWidth="1.5" markerHeight="1" | ||||
|         orient="auto"> | ||||
|       <path d="M 1 -5 L 0 -5 L -5 0 L 0 5 L 1 5 Z" stroke-width="0" [attr.fill]="gradient[0]"/> | ||||
|     </marker> | ||||
|     <marker id="fee-arrow" viewBox="-5 -5 10 10" | ||||
|         refX="0" refY="0" | ||||
|         markerUnits="strokeWidth" | ||||
|         markerWidth="1.5" markerHeight="1" | ||||
|         orient="auto"> | ||||
|     </marker> | ||||
|     <linearGradient id="input-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
| <div class="bowtie-graph"> | ||||
|   <svg *ngIf="inputs && outputs" class="bowtie" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'"> | ||||
|     <defs> | ||||
|       <marker id="input-arrow" viewBox="-5 -5 10 10" | ||||
|           refX="0" refY="0" | ||||
|           markerUnits="strokeWidth" | ||||
|           markerWidth="1.5" markerHeight="1" | ||||
|           orient="auto"> | ||||
|         <path d="M -5 -5 L 0 0 L -5 5 L 1 5 L 1 -5 Z" stroke-width="0" [attr.fill]="gradient[0]"/> | ||||
|       </marker> | ||||
|       <marker id="output-arrow" viewBox="-5 -5 10 10" | ||||
|           refX="0" refY="0" | ||||
|           markerUnits="strokeWidth" | ||||
|           markerWidth="1.5" markerHeight="1" | ||||
|           orient="auto"> | ||||
|         <path d="M 1 -5 L 0 -5 L -5 0 L 0 5 L 1 5 Z" stroke-width="0" [attr.fill]="gradient[0]"/> | ||||
|       </marker> | ||||
|       <marker id="fee-arrow" viewBox="-5 -5 10 10" | ||||
|           refX="0" refY="0" | ||||
|           markerUnits="strokeWidth" | ||||
|           markerWidth="1.5" markerHeight="1" | ||||
|           orient="auto"> | ||||
|       </marker> | ||||
|       <linearGradient id="input-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|         <stop offset="0%" [attr.stop-color]="gradient[0]" /> | ||||
|         <stop offset="100%" [attr.stop-color]="gradient[1]" /> | ||||
|       </linearGradient> | ||||
|       <linearGradient id="output-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|         <stop offset="0%" [attr.stop-color]="gradient[1]" /> | ||||
|         <stop offset="100%" [attr.stop-color]="gradient[0]" /> | ||||
|       </linearGradient> | ||||
|       <linearGradient id="input-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|       <stop offset="0%" [attr.stop-color]="gradient[0]" /> | ||||
|       <stop offset="100%" [attr.stop-color]="gradient[1]" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="output-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|       <stop offset="0%" [attr.stop-color]="gradient[1]" /> | ||||
|       <stop offset="100%" [attr.stop-color]="gradient[0]" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="fee-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|       <stop offset="0%" [attr.stop-color]="gradient[1]" /> | ||||
|       <stop offset="50%" [attr.stop-color]="gradient[1]" /> | ||||
|       <stop offset="100%" stop-color="transparent" /> | ||||
|     </linearGradient> | ||||
|   </defs> | ||||
|   <path [attr.d]="middle.path" class="line middle" [style]="middle.style"/> | ||||
|   <ng-container *ngFor="let input of inputs"> | ||||
|     <path [attr.d]="input.path" class="line {{input.class}}" [style]="input.style" attr.marker-start="url(#{{input.class}}-arrow)"/> | ||||
|   </ng-container> | ||||
|   <ng-container *ngFor="let output of outputs"> | ||||
|     <path [attr.d]="output.path" class="line {{output.class}}" [style]="output.style" attr.marker-start="url(#{{output.class}}-arrow)" /> | ||||
|   </ng-container> | ||||
| </svg> | ||||
|       <stop offset="2%" [attr.stop-color]="gradient[0]" /> | ||||
|         <stop offset="30%" stop-color="white" /> | ||||
|         <stop offset="100%" [attr.stop-color]="gradient[1]" /> | ||||
|       </linearGradient> | ||||
|       <linearGradient id="output-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|         <stop offset="0%" [attr.stop-color]="gradient[1]" /> | ||||
|         <stop offset="70%" stop-color="white" /> | ||||
|         <stop offset="98%" [attr.stop-color]="gradient[0]" /> | ||||
|         <stop offset="100%" [attr.stop-color]="gradient[0]" /> | ||||
|       </linearGradient> | ||||
|       <linearGradient id="fee-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|         <stop offset="0%" [attr.stop-color]="gradient[1]" /> | ||||
|         <stop offset="100%" stop-color="white" /> | ||||
|       </linearGradient> | ||||
|       <linearGradient id="fee-gradient" x1="0%" y1="0%" x2="100%" y2="0%"> | ||||
|         <stop offset="0%" [attr.stop-color]="gradient[1]" /> | ||||
|         <stop offset="50%" [attr.stop-color]="gradient[1]" /> | ||||
|         <stop offset="100%" stop-color="transparent" /> | ||||
|       </linearGradient> | ||||
|     </defs> | ||||
|     <path [attr.d]="middle.path" class="line middle" [style]="middle.style"/> | ||||
|     <ng-container *ngFor="let input of inputs; let i = index"> | ||||
|       <path | ||||
|         [attr.d]="input.path" | ||||
|         class="line {{input.class}}" | ||||
|         [style]="input.style" | ||||
|         attr.marker-start="url(#{{input.class}}-arrow)" | ||||
|         (pointerover)="onHover($event, 'input', i);" | ||||
|         (pointerout)="onBlur($event, 'input', i);" | ||||
|       /> | ||||
|     </ng-container> | ||||
|     <ng-container *ngFor="let output of outputs; let i = index"> | ||||
|       <path | ||||
|         [attr.d]="output.path" | ||||
|         class="line {{output.class}}" | ||||
|         [style]="output.style" | ||||
|         attr.marker-start="url(#{{output.class}}-arrow)" | ||||
|         (pointerover)="onHover($event, 'output', i);" | ||||
|         (pointerout)="onBlur($event, 'output', i);" | ||||
|       /> | ||||
|     </ng-container> | ||||
|   </svg> | ||||
| 
 | ||||
|   <app-tx-bowtie-graph-tooltip | ||||
|     *ngIf=[tooltip] | ||||
|     [line]="hoverLine" | ||||
|     [cursorPosition]="tooltipPosition" | ||||
|   ></app-tx-bowtie-graph-tooltip> | ||||
| </div> | ||||
|  | ||||
| Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.5 KiB | 
| @ -11,5 +11,19 @@ | ||||
|     &.fee { | ||||
|       stroke: url(#fee-gradient); | ||||
|     } | ||||
| 
 | ||||
|     &:hover { | ||||
|       z-index: 10; | ||||
|       cursor: pointer; | ||||
|       &.input { | ||||
|         stroke: url(#input-hover-gradient); | ||||
|       } | ||||
|       &.output { | ||||
|         stroke: url(#output-hover-gradient); | ||||
|       } | ||||
|       &.fee { | ||||
|         stroke: url(#fee-hover-gradient); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Component, OnInit, Input, OnChanges } from '@angular/core'; | ||||
| import { Component, OnInit, Input, OnChanges, HostListener } from '@angular/core'; | ||||
| import { Transaction } from '../../interfaces/electrs.interface'; | ||||
| 
 | ||||
| interface SvgLine { | ||||
| @ -10,6 +10,9 @@ interface SvgLine { | ||||
| interface Xput { | ||||
|   type: 'input' | 'output' | 'fee'; | ||||
|   value?: number; | ||||
|   index?: number; | ||||
|   address?: string; | ||||
|   rest?: number; | ||||
| } | ||||
| 
 | ||||
| const lineLimit = 250; | ||||
| @ -27,12 +30,17 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|   @Input() combinedWeight = 100; | ||||
|   @Input() minWeight = 2; //
 | ||||
|   @Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
 | ||||
|   @Input() tooltip = false; | ||||
| 
 | ||||
|   inputData: Xput[]; | ||||
|   outputData: Xput[]; | ||||
|   inputs: SvgLine[]; | ||||
|   outputs: SvgLine[]; | ||||
|   middle: SvgLine; | ||||
|   midWidth: number; | ||||
|   isLiquid: boolean = false; | ||||
|   hoverLine: Xput | void = null; | ||||
|   tooltipPosition = { x: 0, y: 0 }; | ||||
| 
 | ||||
|   gradientColors = { | ||||
|     '': ['#9339f4', '#105fb0'], | ||||
| @ -59,34 +67,51 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
|   ngOnChanges(): void { | ||||
|     this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet'); | ||||
|     this.gradient = this.gradientColors[this.network]; | ||||
|     this.midWidth = Math.min(50, Math.ceil(this.width / 20)); | ||||
|     this.initGraph(); | ||||
|   } | ||||
| 
 | ||||
|   initGraph(): void { | ||||
|     const totalValue = this.calcTotalValue(this.tx); | ||||
|     let voutWithFee = this.tx.vout.map(v => { return { type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output', value: v?.value } as Xput; }); | ||||
|     let voutWithFee = this.tx.vout.map(v => { | ||||
|       return { | ||||
|         type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output', | ||||
|         value: v?.value, | ||||
|         address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(), | ||||
|       } as Xput; | ||||
|     }); | ||||
| 
 | ||||
|     if (this.tx.fee && !this.isLiquid) { | ||||
|       voutWithFee.unshift({ type: 'fee', value: this.tx.fee }); | ||||
|     } | ||||
|     const outputCount = voutWithFee.length; | ||||
| 
 | ||||
|     let truncatedInputs = this.tx.vin.map(v => { return {type: 'input', value: v?.prevout?.value } as Xput; }); | ||||
|     let truncatedInputs = this.tx.vin.map(v => { | ||||
|       return { | ||||
|         type: 'input', | ||||
|         value: v?.prevout?.value, | ||||
|         address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(), | ||||
|       } as Xput; | ||||
|     }); | ||||
| 
 | ||||
|     if (truncatedInputs.length > lineLimit) { | ||||
|       const valueOfRest = truncatedInputs.slice(lineLimit).reduce((r, v) => { | ||||
|         return r + (v.value || 0); | ||||
|       }, 0); | ||||
|       truncatedInputs = truncatedInputs.slice(0, lineLimit); | ||||
|       truncatedInputs.push({ type: 'input', value: valueOfRest }); | ||||
|       truncatedInputs.push({ type: 'input', value: valueOfRest, rest: this.tx.vin.length - lineLimit }); | ||||
|     } | ||||
|     if (voutWithFee.length > lineLimit) { | ||||
|       const valueOfRest = voutWithFee.slice(lineLimit).reduce((r, v) => { | ||||
|         return r + (v.value || 0); | ||||
|       }, 0); | ||||
|       voutWithFee = voutWithFee.slice(0, lineLimit); | ||||
|       voutWithFee.push({ type: 'output', value: valueOfRest }); | ||||
|       voutWithFee.push({ type: 'output', value: valueOfRest, rest: outputCount - lineLimit }); | ||||
|     } | ||||
| 
 | ||||
|     this.inputData = truncatedInputs; | ||||
|     this.outputData = voutWithFee; | ||||
| 
 | ||||
|     this.inputs = this.initLines('in', truncatedInputs, totalValue, this.maxStrands); | ||||
|     this.outputs = this.initLines('out', voutWithFee, totalValue, this.maxStrands); | ||||
| 
 | ||||
| @ -195,9 +220,32 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { | ||||
| 
 | ||||
|   makeStyle(minWeight, type): string { | ||||
|     if (type === 'fee') { | ||||
|       return `stroke-width: ${minWeight}; stroke: url(#fee-gradient)`; | ||||
|       return `stroke-width: ${minWeight}`; | ||||
|     } else { | ||||
|       return `stroke-width: ${minWeight}`; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @HostListener('pointermove', ['$event']) | ||||
|   onPointerMove(event) { | ||||
|     this.tooltipPosition = { x: event.offsetX, y: event.offsetY }; | ||||
|   } | ||||
| 
 | ||||
|   onHover(event, side, index): void { | ||||
|     if (side === 'input') { | ||||
|       this.hoverLine = { | ||||
|         ...this.inputData[index], | ||||
|         index | ||||
|       }; | ||||
|     } else { | ||||
|       this.hoverLine = { | ||||
|         ...this.outputData[index], | ||||
|         index | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   onBlur(event, side, index): void { | ||||
|     this.hoverLine = null; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -61,6 +61,7 @@ import { FeesBoxComponent } from '../components/fees-box/fees-box.component'; | ||||
| import { DifficultyComponent } from '../components/difficulty/difficulty.component'; | ||||
| import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component'; | ||||
| import { TxBowtieGraphComponent } from '../components/tx-bowtie-graph/tx-bowtie-graph.component'; | ||||
| import { TxBowtieGraphTooltipComponent } from '../components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component'; | ||||
| import { PrivacyPolicyComponent } from '../components/privacy-policy/privacy-policy.component'; | ||||
| import { TrademarkPolicyComponent } from '../components/trademark-policy/trademark-policy.component'; | ||||
| import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component'; | ||||
| @ -134,6 +135,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati | ||||
|     FeesBoxComponent, | ||||
|     DifficultyComponent, | ||||
|     TxBowtieGraphComponent, | ||||
|     TxBowtieGraphTooltipComponent, | ||||
|     TermsOfServiceComponent, | ||||
|     PrivacyPolicyComponent, | ||||
|     TrademarkPolicyComponent, | ||||
| @ -236,6 +238,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati | ||||
|     FeesBoxComponent, | ||||
|     DifficultyComponent, | ||||
|     TxBowtieGraphComponent, | ||||
|     TxBowtieGraphTooltipComponent, | ||||
|     TermsOfServiceComponent, | ||||
|     PrivacyPolicyComponent, | ||||
|     TrademarkPolicyComponent, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user