diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts
index 58921fcfb..b9da7d4e8 100644
--- a/backend/src/api/mempool-blocks.ts
+++ b/backend/src/api/mempool-blocks.ts
@@ -1,6 +1,6 @@
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt';
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 config from '../config';
import { Worker } from 'worker_threads';
@@ -171,7 +171,7 @@ class MempoolBlocks {
for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) {
let added: TransactionClassified[] = [];
let removed: string[] = [];
- const changed: { txid: string, rate: number | undefined, acc: boolean | undefined }[] = [];
+ const changed: TransactionClassified[] = [];
if (mempoolBlocks[i] && !prevBlocks[i]) {
added = mempoolBlocks[i].transactions;
} else if (!mempoolBlocks[i] && prevBlocks[i]) {
@@ -194,14 +194,14 @@ class MempoolBlocks {
if (!prevIds[tx.txid]) {
added.push(tx);
} 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({
- added,
+ added: added.map(this.compressTx),
removed,
- changed,
+ changed: changed.map(this.compressDeltaChange),
});
}
return mempoolBlockDeltas;
@@ -691,6 +691,38 @@ class MempoolBlocks {
});
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();
diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts
index d0e0b7fd8..3091a09cf 100644
--- a/backend/src/api/websocket-handler.ts
+++ b/backend/src/api/websocket-handler.ts
@@ -259,7 +259,7 @@ class WebsocketHandler {
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
response['projected-block-transactions'] = JSON.stringify({
index: index,
- blockTransactions: mBlocksWithTransactions[index]?.transactions || [],
+ blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
});
} else {
client['track-mempool-block'] = null;
@@ -999,7 +999,7 @@ class WebsocketHandler {
if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) {
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, {
index: index,
- blockTransactions: mBlocksWithTransactions[index].transactions,
+ blockTransactions: mBlocksWithTransactions[index].transactions.map(mempoolBlocks.compressTx),
});
} else {
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, {
diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts
index ead0a84ad..71612f25f 100644
--- a/backend/src/mempool.interfaces.ts
+++ b/backend/src/mempool.interfaces.ts
@@ -65,9 +65,9 @@ export interface MempoolBlockWithTransactions extends MempoolBlock {
}
export interface MempoolBlockDelta {
- added: TransactionClassified[];
+ added: TransactionCompressed[];
removed: string[];
- changed: { txid: string, rate: number | undefined, flags?: number }[];
+ changed: MempoolDeltaChange[];
}
interface VinStrippedToScriptsig {
@@ -196,6 +196,11 @@ export interface TransactionClassified extends TransactionStripped {
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
export const TransactionFlags = {
// features
diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html
index f60b04cdd..8c79cd438 100644
--- a/frontend/src/app/components/block-filters/block-filters.component.html
+++ b/frontend/src/app/components/block-filters/block-filters.component.html
@@ -1,4 +1,4 @@
-
0" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
+
0" [class.any-mode]="filterMode === 'or'" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
beta
@@ -14,6 +14,15 @@
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss
index d30dd3305..92964d948 100644
--- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss
+++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss
@@ -7,6 +7,19 @@
justify-content: center;
align-items: center;
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 {
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts
index ac1df2bf5..95305d72f 100644
--- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts
+++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts
@@ -9,6 +9,8 @@ import { Price } from '../../services/price.service';
import { StateService } from '../../services/state.service';
import { Subscription } from 'rxjs';
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 unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
@@ -42,7 +44,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@Input() showFilters: boolean = false;
@Input() excludeFilters: string[] = [];
@Input() filterFlags: bigint | null = null;
- @Input() filterMode: 'and' | 'or' = 'and';
+ @Input() filterMode: FilterMode = 'and';
@Input() blockConversion: Price;
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
@@ -76,11 +78,14 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
filtersAvailable: boolean = true;
activeFilterFlags: bigint | null = null;
+ webGlEnabled = true;
+
constructor(
readonly ngZone: NgZone,
readonly elRef: ElementRef,
private stateService: StateService,
) {
+ this.webGlEnabled = detectWebGL();
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
this.searchSubscription = this.stateService.searchText$.subscribe((text) => {
this.searchText = text;
@@ -119,10 +124,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
}
}
- setFilterFlags(flags?: bigint | null): void {
- this.activeFilterFlags = this.filterFlags || flags || null;
+ setFilterFlags(goggle?: ActiveFilter): void {
+ this.filterMode = goggle?.mode || this.filterMode;
+ this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
if (this.scene) {
- if (this.activeFilterFlags != null) {
+ if (this.activeFilterFlags != null && this.filtersAvailable) {
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
} else {
this.scene.setColorFunction(this.overrideColors);
@@ -157,7 +163,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
// initialize the scene without any entry transition
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) {
this.scene.setup(transactions);
this.readyNextFrame = true;
@@ -500,11 +510,13 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
}
onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
- const x = cssX * window.devicePixelRatio;
- const y = cssY * window.devicePixelRatio;
- const selected = this.scene.getTxAt({ x, y });
- if (selected && selected.txid) {
- this.txClickEvent.emit({ tx: selected, keyModifier });
+ if (this.scene) {
+ const x = cssX * window.devicePixelRatio;
+ const y = cssY * window.devicePixelRatio;
+ const selected = this.scene.getTxAt({ x, y });
+ if (selected && selected.txid) {
+ this.txClickEvent.emit({ tx: selected, keyModifier });
+ }
}
}
@@ -524,7 +536,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
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);
} else {
return defaultColorFunction(
diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
index 7fb036718..29825491c 100644
--- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
+++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
@@ -10,6 +10,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
import { Router } from '@angular/router';
import { Color } from '../block-overview-graph/sprite-types';
import TxView from '../block-overview-graph/tx-view';
+import { FilterMode } from '../../shared/filters.utils';
@Component({
selector: 'app-mempool-block-overview',
@@ -22,7 +23,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
@Input() showFilters: boolean = false;
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
@Input() filterFlags: bigint | undefined = undefined;
- @Input() filterMode: 'and' | 'or' = 'and';
+ @Input() filterMode: FilterMode = 'and';
@Output() txPreviewEvent = new EventEmitter();
@ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
@@ -99,7 +100,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
const inOldBlock = {};
const inNewBlock = {};
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[] = [];
for (const tx of transactionsStripped) {
inNewBlock[tx.txid] = true;
@@ -117,6 +118,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
changed.push({
txid: tx.txid,
rate: tx.rate,
+ flags: tx.flags,
acc: tx.acc
});
}
diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html
index a2c46a198..6425e3b9a 100644
--- a/frontend/src/app/dashboard/dashboard.component.html
+++ b/frontend/src/app/dashboard/dashboard.component.html
@@ -26,7 +26,7 @@
@@ -34,8 +34,8 @@
diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts
index 5dfd68419..1714d3d01 100644
--- a/frontend/src/app/dashboard/dashboard.component.ts
+++ b/frontend/src/app/dashboard/dashboard.component.ts
@@ -7,6 +7,7 @@ import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service';
import { WebsocketService } from '../services/websocket.service';
import { SeoService } from '../services/seo.service';
+import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils';
interface MempoolBlocksData {
blocks: number;
@@ -55,6 +56,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
currentReserves$: Observable;
fullHistory$: Observable;
isLoad: boolean = true;
+ filterSubscription: Subscription;
mempoolInfoSubscription: Subscription;
currencySubscription: Subscription;
currency: string;
@@ -65,13 +67,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
private lastReservesBlockUpdate: number = 0;
goggleResolution = 82;
- goggleCycle = [
- { index: 0, name: 'All' },
- { index: 1, name: 'Consolidations', flag: 0b00000010_00000000_00000000_00000000_00000000n },
- { index: 2, name: 'Coinjoin', flag: 0b00000001_00000000_00000000_00000000_00000000n },
- { index: 3, name: '💩', flag: 0b00000100_00000000_00000000_00000000n | 0b00000010_00000000_00000000_00000000n | 0b00000001_00000000_00000000_00000000n },
+ goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [
+ { index: 0, name: 'All', mode: 'and', filters: [] },
+ { index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] },
+ { index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] },
+ { 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();
@@ -87,6 +91,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngOnDestroy(): void {
+ this.filterSubscription.unsubscribe();
this.mempoolInfoSubscription.unsubscribe();
this.currencySubscription.unsubscribe();
this.websocketService.stopTrackRbfSummary();
@@ -107,6 +112,30 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
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.stateService.mempoolInfo$,
this.stateService.vbytesPerSecond$
@@ -375,6 +404,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
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'])
onResize(): void {
if (window.innerWidth >= 992) {
diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts
index 35bcbe9cc..ff5977332 100644
--- a/frontend/src/app/interfaces/websocket.interface.ts
+++ b/frontend/src/app/interfaces/websocket.interface.ts
@@ -70,9 +70,15 @@ export interface MempoolBlockWithTransactions extends MempoolBlock {
}
export interface MempoolBlockDelta {
- added: TransactionStripped[],
- removed: string[],
- changed?: { txid: string, rate: number | undefined, acc: boolean | undefined }[];
+ added: TransactionStripped[];
+ removed: string[];
+ changed: { txid: string, rate: number, flags: number, acc: boolean }[];
+}
+
+export interface MempoolBlockDeltaCompressed {
+ added: TransactionCompressed[];
+ removed: string[];
+ changed: MempoolDeltaChange[];
}
export interface MempoolInfo {
@@ -97,6 +103,11 @@ export interface TransactionStripped {
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 {
hostname?: string;
gitCommit: string;
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index f87a3dc31..dc1365baa 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -1,7 +1,7 @@
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
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 { Router, NavigationStart } from '@angular/router';
import { isPlatformBrowser } from '@angular/common';
@@ -9,6 +9,7 @@ import { filter, map, scan, shareReplay } from 'rxjs/operators';
import { StorageService } from './storage.service';
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
import { ApiService } from './api.service';
+import { ActiveFilter } from '../shared/filters.utils';
export interface MarkBlockState {
blockHeight?: number;
@@ -150,7 +151,7 @@ export class StateService {
searchFocus$: Subject = new Subject();
menuOpen$: BehaviorSubject = new BehaviorSubject(false);
- activeGoggles$: BehaviorSubject = new BehaviorSubject([]);
+ activeGoggles$: BehaviorSubject = new BehaviorSubject({ mode: 'and', filters: [] });
constructor(
@Inject(PLATFORM_ID) private platformId: any,
diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts
index 3c72252db..11e24ef71 100644
--- a/frontend/src/app/services/websocket.service.ts
+++ b/frontend/src/app/services/websocket.service.ts
@@ -8,6 +8,7 @@ import { ApiService } from './api.service';
import { take } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { CacheService } from './cache.service';
+import { uncompressDeltaChange, uncompressTx } from '../shared/common.utils';
const OFFLINE_RETRY_AFTER_MS = 2000;
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
@@ -382,9 +383,9 @@ export class WebsocketService {
if (response['projected-block-transactions']) {
if (response['projected-block-transactions'].index == this.trackingMempoolBlock) {
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) {
- this.stateService.mempoolBlockDelta$.next(response['projected-block-transactions'].delta);
+ this.stateService.mempoolBlockDelta$.next(uncompressDeltaChange(response['projected-block-transactions'].delta));
}
}
}
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index a04fa1663..18a330fab 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -1,3 +1,5 @@
+import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed, TransactionStripped } from "../interfaces/websocket.interface";
+
export function isMobile(): boolean {
return (window.innerWidth <= 767.98);
}
@@ -152,4 +154,29 @@ export function seoDescriptionNetwork(network: string): string {
return ' ' + network.charAt(0).toUpperCase() + network.slice(1);
}
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],
+ }))
+ };
}
\ No newline at end of file
diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts
index 0b652a192..3930dc8ca 100644
--- a/frontend/src/app/shared/filters.utils.ts
+++ b/frontend/src/app/shared/filters.utils.ts
@@ -7,6 +7,13 @@ export interface Filter {
important?: boolean,
}
+export type FilterMode = 'and' | 'or';
+
+export interface ActiveFilter {
+ mode: FilterMode,
+ filters: string[],
+}
+
// binary flags for transaction classification
export const TransactionFlags = {
// features
@@ -43,6 +50,14 @@ export const TransactionFlags = {
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 } = {
/* features */
rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true },