mirror of
https://github.com/mempool/mempool.git
synced 2024-11-20 10:21:52 +01:00
Merge branch 'master' into nymkappa/fix-username-not-showing
This commit is contained in:
commit
6933d3c395
@ -1,6 +1,6 @@
|
|||||||
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt';
|
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, PoolTag, TransactionClassified } from '../mempool.interfaces';
|
import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, PoolTag, TransactionClassified, TransactionCompressed, MempoolDeltaChange } from '../mempool.interfaces';
|
||||||
import { Common, OnlineFeeStatsCalculator } from './common';
|
import { Common, OnlineFeeStatsCalculator } from './common';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { Worker } from 'worker_threads';
|
import { Worker } from 'worker_threads';
|
||||||
@ -171,7 +171,7 @@ class MempoolBlocks {
|
|||||||
for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) {
|
for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) {
|
||||||
let added: TransactionClassified[] = [];
|
let added: TransactionClassified[] = [];
|
||||||
let removed: string[] = [];
|
let removed: string[] = [];
|
||||||
const changed: { txid: string, rate: number | undefined, acc: boolean | undefined }[] = [];
|
const changed: TransactionClassified[] = [];
|
||||||
if (mempoolBlocks[i] && !prevBlocks[i]) {
|
if (mempoolBlocks[i] && !prevBlocks[i]) {
|
||||||
added = mempoolBlocks[i].transactions;
|
added = mempoolBlocks[i].transactions;
|
||||||
} else if (!mempoolBlocks[i] && prevBlocks[i]) {
|
} else if (!mempoolBlocks[i] && prevBlocks[i]) {
|
||||||
@ -194,14 +194,14 @@ class MempoolBlocks {
|
|||||||
if (!prevIds[tx.txid]) {
|
if (!prevIds[tx.txid]) {
|
||||||
added.push(tx);
|
added.push(tx);
|
||||||
} else if (tx.rate !== prevIds[tx.txid].rate || tx.acc !== prevIds[tx.txid].acc) {
|
} else if (tx.rate !== prevIds[tx.txid].rate || tx.acc !== prevIds[tx.txid].acc) {
|
||||||
changed.push({ txid: tx.txid, rate: tx.rate, acc: tx.acc });
|
changed.push(tx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
mempoolBlockDeltas.push({
|
mempoolBlockDeltas.push({
|
||||||
added,
|
added: added.map(this.compressTx),
|
||||||
removed,
|
removed,
|
||||||
changed,
|
changed: changed.map(this.compressDeltaChange),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return mempoolBlockDeltas;
|
return mempoolBlockDeltas;
|
||||||
@ -691,6 +691,38 @@ class MempoolBlocks {
|
|||||||
});
|
});
|
||||||
return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow };
|
return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public compressTx(tx: TransactionClassified): TransactionCompressed {
|
||||||
|
if (tx.acc) {
|
||||||
|
return [
|
||||||
|
tx.txid,
|
||||||
|
tx.fee,
|
||||||
|
tx.vsize,
|
||||||
|
tx.value,
|
||||||
|
Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100,
|
||||||
|
tx.flags,
|
||||||
|
1
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
tx.txid,
|
||||||
|
tx.fee,
|
||||||
|
tx.vsize,
|
||||||
|
tx.value,
|
||||||
|
Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100,
|
||||||
|
tx.flags,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public compressDeltaChange(tx: TransactionClassified): MempoolDeltaChange {
|
||||||
|
return [
|
||||||
|
tx.txid,
|
||||||
|
Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100,
|
||||||
|
tx.flags,
|
||||||
|
tx.acc ? 1 : 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new MempoolBlocks();
|
export default new MempoolBlocks();
|
||||||
|
@ -259,7 +259,7 @@ class WebsocketHandler {
|
|||||||
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
||||||
response['projected-block-transactions'] = JSON.stringify({
|
response['projected-block-transactions'] = JSON.stringify({
|
||||||
index: index,
|
index: index,
|
||||||
blockTransactions: mBlocksWithTransactions[index]?.transactions || [],
|
blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
client['track-mempool-block'] = null;
|
client['track-mempool-block'] = null;
|
||||||
@ -999,7 +999,7 @@ class WebsocketHandler {
|
|||||||
if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) {
|
if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) {
|
||||||
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
blockTransactions: mBlocksWithTransactions[index].transactions,
|
blockTransactions: mBlocksWithTransactions[index].transactions.map(mempoolBlocks.compressTx),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, {
|
||||||
|
@ -65,9 +65,9 @@ export interface MempoolBlockWithTransactions extends MempoolBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MempoolBlockDelta {
|
export interface MempoolBlockDelta {
|
||||||
added: TransactionClassified[];
|
added: TransactionCompressed[];
|
||||||
removed: string[];
|
removed: string[];
|
||||||
changed: { txid: string, rate: number | undefined, flags?: number }[];
|
changed: MempoolDeltaChange[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VinStrippedToScriptsig {
|
interface VinStrippedToScriptsig {
|
||||||
@ -196,6 +196,11 @@ export interface TransactionClassified extends TransactionStripped {
|
|||||||
flags: number;
|
flags: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [txid, fee, vsize, value, rate, flags, acceleration?]
|
||||||
|
export type TransactionCompressed = [string, number, number, number, number, number, 1?];
|
||||||
|
// [txid, rate, flags, acceleration?]
|
||||||
|
export type MempoolDeltaChange = [string, number, number, (1|0)];
|
||||||
|
|
||||||
// binary flags for transaction classification
|
// binary flags for transaction classification
|
||||||
export const TransactionFlags = {
|
export const TransactionFlags = {
|
||||||
// features
|
// features
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
|
<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.any-mode]="filterMode === 'or'" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
|
||||||
<a *ngIf="menuOpen" [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-mempool-goggles-work" class="info-badges" i18n-ngbTooltip="Mempool Goggles tooltip" ngbTooltip="select filter categories to highlight matching transactions">
|
<a *ngIf="menuOpen" [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-mempool-goggles-work" class="info-badges" i18n-ngbTooltip="Mempool Goggles tooltip" ngbTooltip="select filter categories to highlight matching transactions">
|
||||||
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
|
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
|
||||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
|
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
|
||||||
@ -14,6 +14,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
|
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
|
||||||
|
<h5>Match</h5>
|
||||||
|
<div class="btn-group btn-group-toggle">
|
||||||
|
<label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
|
||||||
|
<input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')">All
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
||||||
|
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')">Any
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<ng-container *ngFor="let group of filterGroups;">
|
<ng-container *ngFor="let group of filterGroups;">
|
||||||
<h5>{{ group.label }}</h5>
|
<h5>{{ group.label }}</h5>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
|
@ -77,6 +77,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.any-mode {
|
||||||
|
.filter-tag {
|
||||||
|
border: solid 1px #1a9436;
|
||||||
|
&.selected {
|
||||||
|
background-color: #1a9436;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-toggle {
|
||||||
|
padding: 0.2em 0.5em;
|
||||||
|
pointer-events: all;
|
||||||
|
line-height: 1.5;
|
||||||
|
background: #181b2daf;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 0.2rem;
|
||||||
|
border-bottom-left-radius: 0.2rem;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: 0.2rem;
|
||||||
|
border-bottom-right-radius: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.blue {
|
||||||
|
border: solid 1px #105fb0;
|
||||||
|
&.active {
|
||||||
|
background: #105fb0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.green {
|
||||||
|
border: solid 1px #1a9436;
|
||||||
|
&.active {
|
||||||
|
background: #1a9436;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
|
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
|
||||||
.menu-toggle {
|
.menu-toggle {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
@ -132,6 +175,11 @@
|
|||||||
.filter-tag {
|
.filter-tag {
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
}
|
}
|
||||||
|
.mode-toggle {
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.tiny {
|
&.tiny {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core';
|
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { FilterGroups, TransactionFilters } from '../../shared/filters.utils';
|
import { ActiveFilter, FilterGroups, FilterMode, TransactionFilters } from '../../shared/filters.utils';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import { Subscription } from 'rxjs';
|
|||||||
export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() cssWidth: number = 800;
|
@Input() cssWidth: number = 800;
|
||||||
@Input() excludeFilters: string[] = [];
|
@Input() excludeFilters: string[] = [];
|
||||||
@Output() onFilterChanged: EventEmitter<bigint | null> = new EventEmitter();
|
@Output() onFilterChanged: EventEmitter<ActiveFilter | null> = new EventEmitter();
|
||||||
|
|
||||||
filterSubscription: Subscription;
|
filterSubscription: Subscription;
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
disabledFilters: { [key: string]: boolean } = {};
|
disabledFilters: { [key: string]: boolean } = {};
|
||||||
activeFilters: string[] = [];
|
activeFilters: string[] = [];
|
||||||
filterFlags: { [key: string]: boolean } = {};
|
filterFlags: { [key: string]: boolean } = {};
|
||||||
|
filterMode: FilterMode = 'and';
|
||||||
menuOpen: boolean = false;
|
menuOpen: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -29,15 +30,16 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.filterSubscription = this.stateService.activeGoggles$.subscribe((activeFilters: string[]) => {
|
this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
|
||||||
|
this.filterMode = active.mode;
|
||||||
for (const key of Object.keys(this.filterFlags)) {
|
for (const key of Object.keys(this.filterFlags)) {
|
||||||
this.filterFlags[key] = false;
|
this.filterFlags[key] = false;
|
||||||
}
|
}
|
||||||
for (const key of activeFilters) {
|
for (const key of active.filters) {
|
||||||
this.filterFlags[key] = !this.disabledFilters[key];
|
this.filterFlags[key] = !this.disabledFilters[key];
|
||||||
}
|
}
|
||||||
this.activeFilters = [...activeFilters.filter(key => !this.disabledFilters[key])];
|
this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
|
||||||
this.onFilterChanged.emit(this.getBooleanFlags());
|
this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +55,12 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFilterMode(mode): void {
|
||||||
|
this.filterMode = mode;
|
||||||
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
||||||
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
||||||
|
}
|
||||||
|
|
||||||
toggleFilter(key): void {
|
toggleFilter(key): void {
|
||||||
const filter = this.filters[key];
|
const filter = this.filters[key];
|
||||||
this.filterFlags[key] = !this.filterFlags[key];
|
this.filterFlags[key] = !this.filterFlags[key];
|
||||||
@ -73,8 +81,8 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.activeFilters = this.activeFilters.filter(f => f != key);
|
this.activeFilters = this.activeFilters.filter(f => f != key);
|
||||||
}
|
}
|
||||||
const booleanFlags = this.getBooleanFlags();
|
const booleanFlags = this.getBooleanFlags();
|
||||||
this.onFilterChanged.emit(booleanFlags);
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
||||||
this.stateService.activeGoggles$.next([...this.activeFilters]);
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
||||||
}
|
}
|
||||||
|
|
||||||
getBooleanFlags(): bigint | null {
|
getBooleanFlags(): bigint | null {
|
||||||
@ -90,7 +98,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@HostListener('document:click', ['$event'])
|
@HostListener('document:click', ['$event'])
|
||||||
onClick(event): boolean {
|
onClick(event): boolean {
|
||||||
// click away from menu
|
// click away from menu
|
||||||
if (!event.target.closest('button')) {
|
if (!event.target.closest('button') && !event.target.closest('label')) {
|
||||||
this.menuOpen = false;
|
this.menuOpen = false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
[auditEnabled]="auditHighlighting"
|
[auditEnabled]="auditHighlighting"
|
||||||
[blockConversion]="blockConversion"
|
[blockConversion]="blockConversion"
|
||||||
></app-block-overview-tooltip>
|
></app-block-overview-tooltip>
|
||||||
<app-block-filters *ngIf="showFilters && filtersAvailable" [excludeFilters]="excludeFilters" [cssWidth]="cssWidth" (onFilterChanged)="setFilterFlags($event)"></app-block-filters>
|
<app-block-filters *ngIf="webGlEnabled && showFilters && filtersAvailable" [excludeFilters]="excludeFilters" [cssWidth]="cssWidth" (onFilterChanged)="setFilterFlags($event)"></app-block-filters>
|
||||||
|
<div *ngIf="!webGlEnabled" class="placeholder">
|
||||||
|
<span i18n="webgl-disabled">Your browser does not support this feature.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,6 +7,19 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-column: 1/-1;
|
grid-column: 1/-1;
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-align {
|
.grid-align {
|
||||||
|
@ -9,6 +9,8 @@ import { Price } from '../../services/price.service';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
|
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
|
||||||
|
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
||||||
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
|
|
||||||
const unmatchedOpacity = 0.2;
|
const unmatchedOpacity = 0.2;
|
||||||
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
||||||
@ -42,7 +44,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
@Input() showFilters: boolean = false;
|
@Input() showFilters: boolean = false;
|
||||||
@Input() excludeFilters: string[] = [];
|
@Input() excludeFilters: string[] = [];
|
||||||
@Input() filterFlags: bigint | null = null;
|
@Input() filterFlags: bigint | null = null;
|
||||||
@Input() filterMode: 'and' | 'or' = 'and';
|
@Input() filterMode: FilterMode = 'and';
|
||||||
@Input() blockConversion: Price;
|
@Input() blockConversion: Price;
|
||||||
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
||||||
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
|
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
|
||||||
@ -76,11 +78,14 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
filtersAvailable: boolean = true;
|
filtersAvailable: boolean = true;
|
||||||
activeFilterFlags: bigint | null = null;
|
activeFilterFlags: bigint | null = null;
|
||||||
|
|
||||||
|
webGlEnabled = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly ngZone: NgZone,
|
readonly ngZone: NgZone,
|
||||||
readonly elRef: ElementRef,
|
readonly elRef: ElementRef,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
) {
|
) {
|
||||||
|
this.webGlEnabled = detectWebGL();
|
||||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
||||||
this.searchSubscription = this.stateService.searchText$.subscribe((text) => {
|
this.searchSubscription = this.stateService.searchText$.subscribe((text) => {
|
||||||
this.searchText = text;
|
this.searchText = text;
|
||||||
@ -119,10 +124,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilterFlags(flags?: bigint | null): void {
|
setFilterFlags(goggle?: ActiveFilter): void {
|
||||||
this.activeFilterFlags = this.filterFlags || flags || null;
|
this.filterMode = goggle?.mode || this.filterMode;
|
||||||
|
this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
if (this.activeFilterFlags != null) {
|
if (this.activeFilterFlags != null && this.filtersAvailable) {
|
||||||
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
|
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
|
||||||
} else {
|
} else {
|
||||||
this.scene.setColorFunction(this.overrideColors);
|
this.scene.setColorFunction(this.overrideColors);
|
||||||
@ -157,7 +163,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
// initialize the scene without any entry transition
|
// initialize the scene without any entry transition
|
||||||
setup(transactions: TransactionStripped[]): void {
|
setup(transactions: TransactionStripped[]): void {
|
||||||
this.filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false);
|
const filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false);
|
||||||
|
if (filtersAvailable !== this.filtersAvailable) {
|
||||||
|
this.setFilterFlags();
|
||||||
|
}
|
||||||
|
this.filtersAvailable = filtersAvailable;
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
this.scene.setup(transactions);
|
this.scene.setup(transactions);
|
||||||
this.readyNextFrame = true;
|
this.readyNextFrame = true;
|
||||||
@ -500,6 +510,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
|
onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
|
||||||
|
if (this.scene) {
|
||||||
const x = cssX * window.devicePixelRatio;
|
const x = cssX * window.devicePixelRatio;
|
||||||
const y = cssY * window.devicePixelRatio;
|
const y = cssY * window.devicePixelRatio;
|
||||||
const selected = this.scene.getTxAt({ x, y });
|
const selected = this.scene.getTxAt({ x, y });
|
||||||
@ -507,6 +518,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.txClickEvent.emit({ tx: selected, keyModifier });
|
this.txClickEvent.emit({ tx: selected, keyModifier });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onTxHover(hoverId: string) {
|
onTxHover(hoverId: string) {
|
||||||
this.txHoverEvent.emit(hoverId);
|
this.txHoverEvent.emit(hoverId);
|
||||||
@ -524,7 +536,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
|
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
|
||||||
return (tx: TxView) => {
|
return (tx: TxView) => {
|
||||||
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (tx.bigintFlags & flags) > 0n)) {
|
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
||||||
return defaultColorFunction(tx);
|
return defaultColorFunction(tx);
|
||||||
} else {
|
} else {
|
||||||
return defaultColorFunction(
|
return defaultColorFunction(
|
||||||
|
@ -10,6 +10,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Color } from '../block-overview-graph/sprite-types';
|
import { Color } from '../block-overview-graph/sprite-types';
|
||||||
import TxView from '../block-overview-graph/tx-view';
|
import TxView from '../block-overview-graph/tx-view';
|
||||||
|
import { FilterMode } from '../../shared/filters.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-mempool-block-overview',
|
selector: 'app-mempool-block-overview',
|
||||||
@ -22,7 +23,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
@Input() showFilters: boolean = false;
|
@Input() showFilters: boolean = false;
|
||||||
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
||||||
@Input() filterFlags: bigint | undefined = undefined;
|
@Input() filterFlags: bigint | undefined = undefined;
|
||||||
@Input() filterMode: 'and' | 'or' = 'and';
|
@Input() filterMode: FilterMode = 'and';
|
||||||
@Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>();
|
@Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>();
|
||||||
|
|
||||||
@ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
|
@ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
|
||||||
@ -99,7 +100,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
const inOldBlock = {};
|
const inOldBlock = {};
|
||||||
const inNewBlock = {};
|
const inNewBlock = {};
|
||||||
const added: TransactionStripped[] = [];
|
const added: TransactionStripped[] = [];
|
||||||
const changed: { txid: string, rate: number | undefined, acc: boolean | undefined }[] = [];
|
const changed: { txid: string, rate: number | undefined, flags: number, acc: boolean | undefined }[] = [];
|
||||||
const removed: string[] = [];
|
const removed: string[] = [];
|
||||||
for (const tx of transactionsStripped) {
|
for (const tx of transactionsStripped) {
|
||||||
inNewBlock[tx.txid] = true;
|
inNewBlock[tx.txid] = true;
|
||||||
@ -117,6 +118,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
changed.push({
|
changed.push({
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
rate: tx.rate,
|
rate: tx.rate,
|
||||||
|
flags: tx.flags,
|
||||||
acc: tx.acc
|
acc: tx.acc
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<div class="quick-filter">
|
<div class="quick-filter">
|
||||||
<div class="btn-group btn-group-toggle">
|
<div class="btn-group btn-group-toggle">
|
||||||
<label class="btn btn-primary btn-xs" [class.active]="filter.index === goggleIndex" *ngFor="let filter of goggleCycle">
|
<label class="btn btn-primary btn-xs" [class.active]="filter.index === goggleIndex" *ngFor="let filter of goggleCycle">
|
||||||
<input type="radio" [value]="'3m'" fragment="3m" (click)="goggleIndex = filter.index" [attr.data-cy]="'3m'"> {{ filter.name }}
|
<input type="radio" [value]="'3m'" fragment="3m" (click)="setFilter(filter.index)" [attr.data-cy]="'3m'"> {{ filter.name }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -34,8 +34,8 @@
|
|||||||
<app-mempool-block-overview
|
<app-mempool-block-overview
|
||||||
[index]="0"
|
[index]="0"
|
||||||
[resolution]="goggleResolution"
|
[resolution]="goggleResolution"
|
||||||
[filterFlags]="goggleCycle[goggleIndex].flag"
|
[filterFlags]="goggleFlags"
|
||||||
filterMode="or"
|
[filterMode]="goggleMode"
|
||||||
></app-mempool-block-overview>
|
></app-mempool-block-overview>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -7,6 +7,7 @@ import { ApiService } from '../services/api.service';
|
|||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
import { WebsocketService } from '../services/websocket.service';
|
import { WebsocketService } from '../services/websocket.service';
|
||||||
import { SeoService } from '../services/seo.service';
|
import { SeoService } from '../services/seo.service';
|
||||||
|
import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils';
|
||||||
|
|
||||||
interface MempoolBlocksData {
|
interface MempoolBlocksData {
|
||||||
blocks: number;
|
blocks: number;
|
||||||
@ -55,6 +56,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
currentReserves$: Observable<CurrentPegs>;
|
currentReserves$: Observable<CurrentPegs>;
|
||||||
fullHistory$: Observable<any>;
|
fullHistory$: Observable<any>;
|
||||||
isLoad: boolean = true;
|
isLoad: boolean = true;
|
||||||
|
filterSubscription: Subscription;
|
||||||
mempoolInfoSubscription: Subscription;
|
mempoolInfoSubscription: Subscription;
|
||||||
currencySubscription: Subscription;
|
currencySubscription: Subscription;
|
||||||
currency: string;
|
currency: string;
|
||||||
@ -65,13 +67,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
private lastReservesBlockUpdate: number = 0;
|
private lastReservesBlockUpdate: number = 0;
|
||||||
|
|
||||||
goggleResolution = 82;
|
goggleResolution = 82;
|
||||||
goggleCycle = [
|
goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [
|
||||||
{ index: 0, name: 'All' },
|
{ index: 0, name: 'All', mode: 'and', filters: [] },
|
||||||
{ index: 1, name: 'Consolidations', flag: 0b00000010_00000000_00000000_00000000_00000000n },
|
{ index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] },
|
||||||
{ index: 2, name: 'Coinjoin', flag: 0b00000001_00000000_00000000_00000000_00000000n },
|
{ index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] },
|
||||||
{ index: 3, name: '💩', flag: 0b00000100_00000000_00000000_00000000n | 0b00000010_00000000_00000000_00000000n | 0b00000001_00000000_00000000_00000000n },
|
{ index: 3, name: 'Data', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] },
|
||||||
];
|
];
|
||||||
goggleIndex = 0; // Math.floor(Math.random() * this.goggleCycle.length);
|
goggleFlags = 0n;
|
||||||
|
goggleMode: FilterMode = 'and';
|
||||||
|
goggleIndex = 0;
|
||||||
|
|
||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
|
|
||||||
@ -87,6 +91,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
this.filterSubscription.unsubscribe();
|
||||||
this.mempoolInfoSubscription.unsubscribe();
|
this.mempoolInfoSubscription.unsubscribe();
|
||||||
this.currencySubscription.unsubscribe();
|
this.currencySubscription.unsubscribe();
|
||||||
this.websocketService.stopTrackRbfSummary();
|
this.websocketService.stopTrackRbfSummary();
|
||||||
@ -107,6 +112,30 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
|
map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
|
||||||
|
const activeFilters = active.filters.sort().join(',');
|
||||||
|
for (const goggle of this.goggleCycle) {
|
||||||
|
if (goggle.mode === active.mode) {
|
||||||
|
const goggleFilters = goggle.filters.sort().join(',');
|
||||||
|
if (goggleFilters === activeFilters) {
|
||||||
|
this.goggleIndex = goggle.index;
|
||||||
|
this.goggleFlags = toFlags(goggle.filters);
|
||||||
|
this.goggleMode = goggle.mode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.goggleCycle.push({
|
||||||
|
index: this.goggleCycle.length,
|
||||||
|
name: 'Custom',
|
||||||
|
mode: active.mode,
|
||||||
|
filters: active.filters,
|
||||||
|
});
|
||||||
|
this.goggleIndex = this.goggleCycle.length - 1;
|
||||||
|
this.goggleFlags = toFlags(active.filters);
|
||||||
|
this.goggleMode = active.mode;
|
||||||
|
});
|
||||||
|
|
||||||
this.mempoolInfoData$ = combineLatest([
|
this.mempoolInfoData$ = combineLatest([
|
||||||
this.stateService.mempoolInfo$,
|
this.stateService.mempoolInfo$,
|
||||||
this.stateService.vbytesPerSecond$
|
this.stateService.vbytesPerSecond$
|
||||||
@ -375,6 +404,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
return Array.from({ length: num }, (_, i) => i + 1);
|
return Array.from({ length: num }, (_, i) => i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFilter(index): void {
|
||||||
|
const selected = this.goggleCycle[index];
|
||||||
|
this.stateService.activeGoggles$.next(selected);
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(): void {
|
onResize(): void {
|
||||||
if (window.innerWidth >= 992) {
|
if (window.innerWidth >= 992) {
|
||||||
|
@ -70,9 +70,15 @@ export interface MempoolBlockWithTransactions extends MempoolBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MempoolBlockDelta {
|
export interface MempoolBlockDelta {
|
||||||
added: TransactionStripped[],
|
added: TransactionStripped[];
|
||||||
removed: string[],
|
removed: string[];
|
||||||
changed?: { txid: string, rate: number | undefined, acc: boolean | undefined }[];
|
changed: { txid: string, rate: number, flags: number, acc: boolean }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MempoolBlockDeltaCompressed {
|
||||||
|
added: TransactionCompressed[];
|
||||||
|
removed: string[];
|
||||||
|
changed: MempoolDeltaChange[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MempoolInfo {
|
export interface MempoolInfo {
|
||||||
@ -97,6 +103,11 @@ export interface TransactionStripped {
|
|||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [txid, fee, vsize, value, rate, flags, acceleration?]
|
||||||
|
export type TransactionCompressed = [string, number, number, number, number, number, 1?];
|
||||||
|
// [txid, rate, flags, acceleration?]
|
||||||
|
export type MempoolDeltaChange = [string, number, number, (1|0)];
|
||||||
|
|
||||||
export interface IBackendInfo {
|
export interface IBackendInfo {
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
gitCommit: string;
|
gitCommit: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionCompressed, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
@ -9,6 +9,7 @@ import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
|||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
|
import { ActiveFilter } from '../shared/filters.utils';
|
||||||
|
|
||||||
export interface MarkBlockState {
|
export interface MarkBlockState {
|
||||||
blockHeight?: number;
|
blockHeight?: number;
|
||||||
@ -150,7 +151,7 @@ export class StateService {
|
|||||||
searchFocus$: Subject<boolean> = new Subject<boolean>();
|
searchFocus$: Subject<boolean> = new Subject<boolean>();
|
||||||
menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
menuOpen$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
activeGoggles$: BehaviorSubject<string[]> = new BehaviorSubject([]);
|
activeGoggles$: BehaviorSubject<ActiveFilter> = new BehaviorSubject({ mode: 'and', filters: [] });
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
|
@ -8,6 +8,7 @@ import { ApiService } from './api.service';
|
|||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
||||||
import { CacheService } from './cache.service';
|
import { CacheService } from './cache.service';
|
||||||
|
import { uncompressDeltaChange, uncompressTx } from '../shared/common.utils';
|
||||||
|
|
||||||
const OFFLINE_RETRY_AFTER_MS = 2000;
|
const OFFLINE_RETRY_AFTER_MS = 2000;
|
||||||
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
||||||
@ -382,9 +383,9 @@ export class WebsocketService {
|
|||||||
if (response['projected-block-transactions']) {
|
if (response['projected-block-transactions']) {
|
||||||
if (response['projected-block-transactions'].index == this.trackingMempoolBlock) {
|
if (response['projected-block-transactions'].index == this.trackingMempoolBlock) {
|
||||||
if (response['projected-block-transactions'].blockTransactions) {
|
if (response['projected-block-transactions'].blockTransactions) {
|
||||||
this.stateService.mempoolBlockTransactions$.next(response['projected-block-transactions'].blockTransactions);
|
this.stateService.mempoolBlockTransactions$.next(response['projected-block-transactions'].blockTransactions.map(uncompressTx));
|
||||||
} else if (response['projected-block-transactions'].delta) {
|
} else if (response['projected-block-transactions'].delta) {
|
||||||
this.stateService.mempoolBlockDelta$.next(response['projected-block-transactions'].delta);
|
this.stateService.mempoolBlockDelta$.next(uncompressDeltaChange(response['projected-block-transactions'].delta));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed, TransactionStripped } from "../interfaces/websocket.interface";
|
||||||
|
|
||||||
export function isMobile(): boolean {
|
export function isMobile(): boolean {
|
||||||
return (window.innerWidth <= 767.98);
|
return (window.innerWidth <= 767.98);
|
||||||
}
|
}
|
||||||
@ -153,3 +155,28 @@ export function seoDescriptionNetwork(network: string): string {
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uncompressTx(tx: TransactionCompressed): TransactionStripped {
|
||||||
|
return {
|
||||||
|
txid: tx[0],
|
||||||
|
fee: tx[1],
|
||||||
|
vsize: tx[2],
|
||||||
|
value: tx[3],
|
||||||
|
rate: tx[4],
|
||||||
|
flags: tx[5],
|
||||||
|
acc: !!tx[6],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uncompressDeltaChange(delta: MempoolBlockDeltaCompressed): MempoolBlockDelta {
|
||||||
|
return {
|
||||||
|
added: delta.added.map(uncompressTx),
|
||||||
|
removed: delta.removed,
|
||||||
|
changed: delta.changed.map(tx => ({
|
||||||
|
txid: tx[0],
|
||||||
|
rate: tx[1],
|
||||||
|
flags: tx[2],
|
||||||
|
acc: !!tx[3],
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
@ -7,6 +7,13 @@ export interface Filter {
|
|||||||
important?: boolean,
|
important?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FilterMode = 'and' | 'or';
|
||||||
|
|
||||||
|
export interface ActiveFilter {
|
||||||
|
mode: FilterMode,
|
||||||
|
filters: string[],
|
||||||
|
}
|
||||||
|
|
||||||
// binary flags for transaction classification
|
// binary flags for transaction classification
|
||||||
export const TransactionFlags = {
|
export const TransactionFlags = {
|
||||||
// features
|
// features
|
||||||
@ -43,6 +50,14 @@ export const TransactionFlags = {
|
|||||||
sighash_acp: 0b00010000_00000000_00000000_00000000_00000000_00000000n,
|
sighash_acp: 0b00010000_00000000_00000000_00000000_00000000_00000000n,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function toFlags(filters: string[]): bigint {
|
||||||
|
let flag = 0n;
|
||||||
|
for (const filter of filters) {
|
||||||
|
flag |= TransactionFlags[filter];
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
export const TransactionFilters: { [key: string]: Filter } = {
|
export const TransactionFilters: { [key: string]: Filter } = {
|
||||||
/* features */
|
/* features */
|
||||||
rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true },
|
rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true },
|
||||||
|
Loading…
Reference in New Issue
Block a user