Resolve conflicts in dashboard component
This commit is contained in:
commit
d55d5db01d
@ -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();
|
||||||
|
@ -285,7 +285,7 @@ class StatisticsApi {
|
|||||||
|
|
||||||
public async $list2H(): Promise<OptimizedStatistic[]> {
|
public async $list2H(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 120`;
|
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL 2 HOUR) AND NOW() ORDER BY statistics.added DESC`;
|
||||||
const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
|
||||||
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
|
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -296,7 +296,7 @@ class StatisticsApi {
|
|||||||
|
|
||||||
public async $list24H(): Promise<OptimizedStatistic[]> {
|
public async $list24H(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 1440`;
|
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL 24 HOUR) AND NOW() ORDER BY statistics.added DESC`;
|
||||||
const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
|
||||||
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
|
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -6,6 +6,7 @@ import statisticsApi from './statistics-api';
|
|||||||
|
|
||||||
class Statistics {
|
class Statistics {
|
||||||
protected intervalTimer: NodeJS.Timer | undefined;
|
protected intervalTimer: NodeJS.Timer | undefined;
|
||||||
|
protected lastRun: number = 0;
|
||||||
protected newStatisticsEntryCallback: ((stats: OptimizedStatistic) => void) | undefined;
|
protected newStatisticsEntryCallback: ((stats: OptimizedStatistic) => void) | undefined;
|
||||||
|
|
||||||
public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
|
public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
|
||||||
@ -23,15 +24,21 @@ class Statistics {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.runStatistics();
|
this.runStatistics();
|
||||||
this.intervalTimer = setInterval(() => {
|
this.intervalTimer = setInterval(() => {
|
||||||
this.runStatistics();
|
this.runStatistics(true);
|
||||||
}, 1 * 60 * 1000);
|
}, 1 * 60 * 1000);
|
||||||
}, difference);
|
}, difference);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runStatistics(): Promise<void> {
|
public async runStatistics(skipIfRecent = false): Promise<void> {
|
||||||
if (!memPool.isInSync()) {
|
if (!memPool.isInSync()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skipIfRecent && new Date().getTime() / 1000 - this.lastRun < 30) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastRun = new Date().getTime() / 1000;
|
||||||
const currentMempool = memPool.getMempool();
|
const currentMempool = memPool.getMempool();
|
||||||
const txPerSecond = memPool.getTxPerSecond();
|
const txPerSecond = memPool.getTxPerSecond();
|
||||||
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
||||||
|
@ -23,6 +23,7 @@ import priceUpdater from '../tasks/price-updater';
|
|||||||
import { ApiPrice } from '../repositories/PricesRepository';
|
import { ApiPrice } from '../repositories/PricesRepository';
|
||||||
import accelerationApi from './services/acceleration';
|
import accelerationApi from './services/acceleration';
|
||||||
import mempool from './mempool';
|
import mempool from './mempool';
|
||||||
|
import statistics from './statistics/statistics';
|
||||||
|
|
||||||
interface AddressTransactions {
|
interface AddressTransactions {
|
||||||
mempool: MempoolTransactionExtended[],
|
mempool: MempoolTransactionExtended[],
|
||||||
@ -259,7 +260,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;
|
||||||
@ -723,6 +724,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.printLogs();
|
this.printLogs();
|
||||||
|
await statistics.runStatistics();
|
||||||
|
|
||||||
const _memPool = memPool.getMempool();
|
const _memPool = memPool.getMempool();
|
||||||
|
|
||||||
@ -999,7 +1001,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}`, {
|
||||||
@ -1014,6 +1016,8 @@ class WebsocketHandler {
|
|||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await statistics.runStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
// takes a dictionary of JSON serialized values
|
// takes a dictionary of JSON serialized values
|
||||||
|
@ -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,11 +510,13 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
|
onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
|
||||||
const x = cssX * window.devicePixelRatio;
|
if (this.scene) {
|
||||||
const y = cssY * window.devicePixelRatio;
|
const x = cssX * window.devicePixelRatio;
|
||||||
const selected = this.scene.getTxAt({ x, y });
|
const y = cssY * window.devicePixelRatio;
|
||||||
if (selected && selected.txid) {
|
const selected = this.scene.getTxAt({ x, y });
|
||||||
this.txClickEvent.emit({ tx: selected, keyModifier });
|
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) {
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,6 @@
|
|||||||
background-color: #f1c40f;
|
background-color: #f1c40f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-platinium {
|
.badge-platinum {
|
||||||
background-color: #653b9c;
|
background-color: #653b9c;
|
||||||
}
|
}
|
@ -25,7 +25,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>
|
||||||
@ -33,8 +33,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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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;
|
||||||
@ -58,6 +59,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
federationUtxosNumber$: Observable<number>;
|
federationUtxosNumber$: Observable<number>;
|
||||||
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;
|
||||||
@ -68,13 +70,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();
|
||||||
|
|
||||||
@ -90,6 +94,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();
|
||||||
@ -110,6 +115,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$
|
||||||
@ -393,6 +422,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) {
|
||||||
|
@ -9871,7 +9871,403 @@ export const restApiDocsData = [
|
|||||||
codeSampleBisq: emptyCodeSample,
|
codeSampleBisq: emptyCodeSample,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
category: "accelerator",
|
||||||
|
fragment: "accelerator",
|
||||||
|
title: "Accelerator",
|
||||||
|
showConditions: [""],
|
||||||
|
options: { officialOnly: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: { officialOnly: true },
|
||||||
|
type: "endpoint",
|
||||||
|
category: "accelerator",
|
||||||
|
httpRequestMethod: "GET",
|
||||||
|
fragment: "accelerator-deposit-history",
|
||||||
|
title: "GET Deposit History",
|
||||||
|
description: {
|
||||||
|
default: "<p>Returns a list of deposits the user has made as prepayment for the accelerator service.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/services/accelerator/deposit-history",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `/api/v1/services/accelerator/deposit-history`,
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [],
|
||||||
|
headers: "api_key: stacksats",
|
||||||
|
response: `[
|
||||||
|
{
|
||||||
|
"type": "Bitcoin",
|
||||||
|
"invoiceId": "CCunucVyNw7jUiUz64mmHz",
|
||||||
|
"amount": 10311031,
|
||||||
|
"status": "pending",
|
||||||
|
"date": 1706372653000,
|
||||||
|
"link": "/payment/bitcoin/CCunucVyNw7jUiUz64mmHz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Bitcoin",
|
||||||
|
"invoiceId": "SG1U27R9PdWi3gH3jB9tm9",
|
||||||
|
"amount": 21000000,
|
||||||
|
"status": "paid",
|
||||||
|
"date": 1706372582000,
|
||||||
|
"link": null
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: { officialOnly: true },
|
||||||
|
type: "endpoint",
|
||||||
|
category: "accelerator",
|
||||||
|
httpRequestMethod: "GET",
|
||||||
|
fragment: "accelerator-balance",
|
||||||
|
title: "GET Available Balance",
|
||||||
|
description: {
|
||||||
|
default: "<p>Returns the user's currently available balance, currently locked funds, and total fees paid so far.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/services/accelerator/balance",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `/api/v1/services/accelerator/balance`,
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [],
|
||||||
|
headers: "api_key: stacksats",
|
||||||
|
response: `{
|
||||||
|
"balance": 99900000,
|
||||||
|
"hold": 101829,
|
||||||
|
"feesPaid": 133721
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: { officialOnly: true },
|
||||||
|
type: "endpoint",
|
||||||
|
category: "accelerator",
|
||||||
|
httpRequestMethod: "POST",
|
||||||
|
fragment: "accelerator-estimate",
|
||||||
|
title: "POST Calculate Estimated Costs",
|
||||||
|
description: {
|
||||||
|
default: "<p>Returns estimated costs to accelerate a transaction.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/services/accelerator/estimate",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `%{1}" "[[hostname]][[baseNetworkUrl]]/api/v1/services/accelerator/estimate`, //custom interpolation technique handled in replaceCurlPlaceholder()
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29"],
|
||||||
|
headers: "api_key: stacksats",
|
||||||
|
response: `{
|
||||||
|
"txSummary": {
|
||||||
|
"txid": "ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29",
|
||||||
|
"effectiveVsize": 154,
|
||||||
|
"effectiveFee": 154,
|
||||||
|
"ancestorCount": 1
|
||||||
|
},
|
||||||
|
"cost": 3850,
|
||||||
|
"targetFeeRate": 26,
|
||||||
|
"nextBlockFee": 4004,
|
||||||
|
"userBalance": 99900000,
|
||||||
|
"mempoolBaseFee": 40000,
|
||||||
|
"vsizeFee": 50000,
|
||||||
|
"hasAccess": true
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: { officialOnly: true },
|
||||||
|
type: "endpoint",
|
||||||
|
category: "accelerator",
|
||||||
|
httpRequestMethod: "POST",
|
||||||
|
fragment: "accelerator-accelerate",
|
||||||
|
title: "POST Accelerate A Transaction",
|
||||||
|
description: {
|
||||||
|
default: "<p>Sends a request to accelerate a transaction.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/services/accelerator/accelerate",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `%{1}" "[[hostname]][[baseNetworkUrl]]/api/v1/services/accelerator/accelerate`, //custom interpolation technique handled in replaceCurlPlaceholder()
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29&userBid=21000000"],
|
||||||
|
headers: "api_key: stacksats",
|
||||||
|
response: `HTTP/1.1 200 OK`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: { officialOnly: true },
|
||||||
|
type: "endpoint",
|
||||||
|
category: "accelerator",
|
||||||
|
httpRequestMethod: "GET",
|
||||||
|
fragment: "accelerator-history",
|
||||||
|
title: "GET Private Acceleration History",
|
||||||
|
description: {
|
||||||
|
default: "<p>Returns the user's past acceleration requests.</p><p>Pass one of the following for <code>:status</code>: <code>all</code>, <code>requested</code>, <code>accelerating</code>, <code>mined</code>, <code>completed</code>, <code>failed</code>. Pass <code>true</code> in <code>:details</code> to get a detailed <code>history</code> of the acceleration request.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/services/accelerator/history?status=:status&details=:details",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `/api/v1/services/accelerator/history?status=all&details=true`,
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [],
|
||||||
|
headers: "api_key: stacksats",
|
||||||
|
response: `[
|
||||||
|
{
|
||||||
|
"id": 89,
|
||||||
|
"user_id": 1,
|
||||||
|
"txid": "ae2639469ec000ed1d14e2550cbb01794e1cd288a00cdc7cce18398ba3cc2ffe",
|
||||||
|
"status": "failed",
|
||||||
|
"estimated_fee": 247,
|
||||||
|
"fee_paid": 0,
|
||||||
|
"added": 1706378712,
|
||||||
|
"last_updated": 1706378712,
|
||||||
|
"confirmations": 4,
|
||||||
|
"base_fee": 0,
|
||||||
|
"vsize_fee": 0,
|
||||||
|
"max_bid": 7000,
|
||||||
|
"effective_vsize": 135,
|
||||||
|
"effective_fee": 3128,
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"event": "user-requested-acceleration",
|
||||||
|
"timestamp": 1706378712
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event": "accepted_test-api-key",
|
||||||
|
"timestamp": 1706378712
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event": "failed-at-block-827672",
|
||||||
|
"timestamp": 1706380261
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 88,
|
||||||
|
"user_id": 1,
|
||||||
|
"txid": "c5840e89173331760e959a190b24e2a289121277ed7f8a095fe289b37cee9fde",
|
||||||
|
"status": "completed",
|
||||||
|
"estimated_fee": 223,
|
||||||
|
"fee_paid": 140019,
|
||||||
|
"added": 1706378704,
|
||||||
|
"last_updated": 1706380231,
|
||||||
|
"confirmations": 6,
|
||||||
|
"base_fee": 40000,
|
||||||
|
"vsize_fee": 100000,
|
||||||
|
"max_bid": 14000,
|
||||||
|
"effective_vsize": 135,
|
||||||
|
"effective_fee": 3152,
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"event": "user-requested-acceleration",
|
||||||
|
"timestamp": 1706378704
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event": "accepted_test-api-key",
|
||||||
|
"timestamp": 1706378704
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event": "complete-at-block-827670",
|
||||||
|
"timestamp": 1706380231
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 87,
|
||||||
|
"user_id": 1,
|
||||||
|
"txid": "178b5b9b310f0d667d7ea563a2cdcc17bc8cd15261b58b1653860a724ca83458",
|
||||||
|
"status": "completed",
|
||||||
|
"estimated_fee": 115,
|
||||||
|
"fee_paid": 90062,
|
||||||
|
"added": 1706378684,
|
||||||
|
"last_updated": 1706380231,
|
||||||
|
"confirmations": 6,
|
||||||
|
"base_fee": 40000,
|
||||||
|
"vsize_fee": 50000,
|
||||||
|
"max_bid": 14000,
|
||||||
|
"effective_vsize": 135,
|
||||||
|
"effective_fee": 3260,
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"event": "user-requested-acceleration",
|
||||||
|
"timestamp": 1706378684
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event": "accepted_test-api-key",
|
||||||
|
"timestamp": 1706378684
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event": "complete-at-block-827670",
|
||||||
|
"timestamp": 1706380231
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: { officialOnly: true },
|
||||||
|
type: "endpoint",
|
||||||
|
category: "accelerator",
|
||||||
|
httpRequestMethod: "GET",
|
||||||
|
fragment: "accelerator-pending",
|
||||||
|
title: "GET Pending Accelerations",
|
||||||
|
description: {
|
||||||
|
default: "<p>Returns all transactions currently being accelerated.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/services/accelerator/accelerations",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `/api/v1/services/accelerator/accelerations`,
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [],
|
||||||
|
headers: '',
|
||||||
|
response: `[
|
||||||
|
{
|
||||||
|
"txid": "8a183c8ae929a2afb857e7f2acd440aaefdf2797f8f7eab1c5f95ff8602abc81",
|
||||||
|
"added": 1707558316,
|
||||||
|
"feeDelta": 3500,
|
||||||
|
"effectiveVsize": 111,
|
||||||
|
"effectiveFee": 1671,
|
||||||
|
"pools": [
|
||||||
|
111
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txid": "6097f295e21bdd8d725bd8d9ad4dd72b05bd795dc648bfef52150a9b2b7f7a45",
|
||||||
|
"added": 1707560464,
|
||||||
|
"feeDelta": 60000,
|
||||||
|
"effectiveVsize": 812,
|
||||||
|
"effectiveFee": 7790,
|
||||||
|
"pools": [
|
||||||
|
111
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
options: { officialOnly: true },
|
||||||
|
type: "endpoint",
|
||||||
|
category: "accelerator",
|
||||||
|
httpRequestMethod: "GET",
|
||||||
|
fragment: "accelerator-public-history",
|
||||||
|
title: "GET Public Acceleration History",
|
||||||
|
description: {
|
||||||
|
default: `<p>Returns all past accelerated transactions.
|
||||||
|
Filters can be applied:<ul>
|
||||||
|
<li><code>status</code>: <code>all</code>, <code>requested</code>, <code>accelerating</code>, <code>mined</code>, <code>completed</code>, <code>failed</code></li>
|
||||||
|
<li><code>timeframe</code>: <code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>, <code>all</code></li>
|
||||||
|
<li><code>poolUniqueId</code>: any id from <a target="_blank" href="https://github.com/mempool/mining-pools/blob/master/pools-v2.json">https://github.com/mempool/mining-pools/blob/master/pools-v2.json</a>
|
||||||
|
<li><code>blockHash</code>: a block hash</a>
|
||||||
|
<li><code>blockHeight</code>: a block height</a>
|
||||||
|
<li><code>page</code>: the requested page number if using pagination</a>
|
||||||
|
<li><code>pageLength</code>: the page lenght if using pagination</a>
|
||||||
|
</ul></p>`
|
||||||
|
},
|
||||||
|
urlString: "/v1/services/accelerator/accelerations/history",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `/api/v1/services/accelerator/accelerations/history?blockHash=00000000000000000000482f0746d62141694b9210a813b97eb8445780a32003`,
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [],
|
||||||
|
headers: '',
|
||||||
|
response: `[
|
||||||
|
{
|
||||||
|
"txid": "d7e1796d8eb4a09d4e6c174e36cfd852f1e6e6c9f7df4496339933cd32cbdd1d",
|
||||||
|
"status": "completed",
|
||||||
|
"feePaid": 53239,
|
||||||
|
"added": 1707421053,
|
||||||
|
"lastUpdated": 1707422952,
|
||||||
|
"baseFee": 50000,
|
||||||
|
"vsizeFee": 0,
|
||||||
|
"effectiveFee": 146,
|
||||||
|
"effectiveVsize": 141,
|
||||||
|
"feeDelta": 14000,
|
||||||
|
"blockHash": "00000000000000000000482f0746d62141694b9210a813b97eb8445780a32003",
|
||||||
|
"blockHeight": 829559,
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"pool_unique_id": 111,
|
||||||
|
"username": "foundryusa"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const faqData = [
|
export const faqData = [
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div *ngFor="let item of tabData">
|
<div *ngFor="let item of tabData">
|
||||||
<p *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</p>
|
<p *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 ) && ( !item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ))">{{ item.title }}</p>
|
||||||
<a *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 ) && ( !item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ) || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('auditOnly') && item.options.auditOnly && auditEnabled ) )" [routerLink]="['./']" fragment="{{ item.fragment }}" (click)="navLinkClick($event)">{{ item.title }}</a>
|
<a *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 ) && ( !item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ) || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('auditOnly') && item.options.auditOnly && auditEnabled ) )" [routerLink]="['./']" fragment="{{ item.fragment }}" (click)="navLinkClick($event)">{{ item.title }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,54 +43,56 @@
|
|||||||
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an <a href="https://mempool.space/enterprise">enterprise sponsorship</a> if you need higher API limits.</p>
|
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an <a href="https://mempool.space/enterprise">enterprise sponsorship</a> if you need higher API limits.</p>
|
||||||
|
|
||||||
<div class="doc-item-container" *ngFor="let item of restDocs">
|
<div class="doc-item-container" *ngFor="let item of restDocs">
|
||||||
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
|
<div *ngIf="!item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance )">
|
||||||
<div *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" class="endpoint-container" id="{{ item.fragment }}">
|
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
|
||||||
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}">{{ item.title }} <span>{{ item.category }}</span></a>
|
<div *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" class="endpoint-container" id="{{ item.fragment }}">
|
||||||
<div class="endpoint-content">
|
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}">{{ item.title }} <span>{{ item.category }}</span></a>
|
||||||
<div class="endpoint">
|
<div class="endpoint-content">
|
||||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
<div class="endpoint">
|
||||||
<ng-container *ngIf="item.httpRequestMethod === 'GET' && network.val === 'bisq' && item.codeExample.hasOwnProperty('bisq');else liquid_link_example" #bisq_link_example>
|
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||||
<a [href]="wrapUrl(network.val, item.codeExample.bisq)" target="_blank" rel="nofollow">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</a>
|
<ng-container *ngIf="item.httpRequestMethod === 'GET' && network.val === 'bisq' && item.codeExample.hasOwnProperty('bisq');else liquid_link_example" #bisq_link_example>
|
||||||
|
<a [href]="wrapUrl(network.val, item.codeExample.bisq)" target="_blank" rel="nofollow">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</a>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #liquid_link_example>
|
||||||
|
<ng-container *ngIf="item.httpRequestMethod === 'GET' && network.val === 'liquid' && item.codeExample.hasOwnProperty('liquid');else default_link_example">
|
||||||
|
<a [href]="wrapUrl(network.val, item.codeExample.liquid)" target="_blank" rel="nofollow" *ngIf="item.fragment !== 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</a>
|
||||||
|
<p *ngIf="item.fragment === 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</p>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #default_link_example>
|
||||||
|
<ng-container *ngIf="item.httpRequestMethod === 'GET'">
|
||||||
|
<a [href]="wrapUrl(network.val, item.codeExample.default)" target="_blank" rel="nofollow" *ngIf="item.fragment !== 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</a>
|
||||||
|
<p *ngIf="item.fragment === 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</p>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
<div *ngIf="item.httpRequestMethod === 'POST'">{{ item.httpRequestMethod }} {{ item.urlString }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<div class="subtitle" i18n>Description</div>
|
||||||
|
<ng-container *ngIf="network.val === 'bisq' && item.description.hasOwnProperty('bisq');else liquid_description" #bisq_description>
|
||||||
|
<div [innerHTML]="item.description.bisq" i18n></div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #liquid_description>
|
||||||
|
<ng-container *ngIf="network.val === 'liquid' && item.description.hasOwnProperty('liquid');else default_description">
|
||||||
|
<div [innerHTML]="item.description.liquid" i18n></div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #default_description>
|
||||||
|
<div [innerHTML]="item.description.default" i18n></div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="network.val === 'bisq' && item.codeExample.hasOwnProperty('bisq');else liquid_code_example" #bisq_code_example>
|
||||||
|
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.bisq" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #liquid_link_example>
|
<ng-template #liquid_code_example>
|
||||||
<ng-container *ngIf="item.httpRequestMethod === 'GET' && network.val === 'liquid' && item.codeExample.hasOwnProperty('liquid');else default_link_example">
|
<ng-container *ngIf="network.val === 'liquid' && item.codeExample.hasOwnProperty('liquid');else default_code_example">
|
||||||
<a [href]="wrapUrl(network.val, item.codeExample.liquid)" target="_blank" rel="nofollow" *ngIf="item.fragment !== 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</a>
|
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.liquid" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
||||||
<p *ngIf="item.fragment === 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</p>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #default_link_example>
|
<ng-template #default_code_example>
|
||||||
<ng-container *ngIf="item.httpRequestMethod === 'GET'">
|
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.default" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
||||||
<a [href]="wrapUrl(network.val, item.codeExample.default)" target="_blank" rel="nofollow" *ngIf="item.fragment !== 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</a>
|
|
||||||
<p *ngIf="item.fragment === 'get-cpfp'">{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}</p>
|
|
||||||
</ng-container>
|
|
||||||
</ng-template>
|
|
||||||
<div *ngIf="item.httpRequestMethod === 'POST'">{{ item.httpRequestMethod }} {{ item.urlString }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="description">
|
|
||||||
<div class="subtitle" i18n>Description</div>
|
|
||||||
<ng-container *ngIf="network.val === 'bisq' && item.description.hasOwnProperty('bisq');else liquid_description" #bisq_description>
|
|
||||||
<div [innerHTML]="item.description.bisq" i18n></div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #liquid_description>
|
|
||||||
<ng-container *ngIf="network.val === 'liquid' && item.description.hasOwnProperty('liquid');else default_description">
|
|
||||||
<div [innerHTML]="item.description.liquid" i18n></div>
|
|
||||||
</ng-container>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #default_description>
|
|
||||||
<div [innerHTML]="item.description.default" i18n></div>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="network.val === 'bisq' && item.codeExample.hasOwnProperty('bisq');else liquid_code_example" #bisq_code_example>
|
|
||||||
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.bisq" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
|
||||||
</ng-container>
|
|
||||||
<ng-template #liquid_code_example>
|
|
||||||
<ng-container *ngIf="network.val === 'liquid' && item.codeExample.hasOwnProperty('liquid');else default_code_example">
|
|
||||||
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.liquid" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
|
||||||
</ng-container>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #default_code_example>
|
|
||||||
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.default" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -311,27 +311,29 @@ yarn add @mempool/liquid.js`;
|
|||||||
text = text.replace('%{' + indexNumber + '}', textReplace);
|
text = text.replace('%{' + indexNumber + '}', textReplace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headersString = code.headers ? ` -H "${code.headers}"` : ``;
|
||||||
|
|
||||||
if (this.env.BASE_MODULE === 'mempool') {
|
if (this.env.BASE_MODULE === 'mempool') {
|
||||||
if (this.network === 'main' || this.network === '') {
|
if (this.network === 'main' || this.network === '') {
|
||||||
if (this.method === 'POST') {
|
if (this.method === 'POST') {
|
||||||
return `curl -X POST -sSLd "${text}"`;
|
return `curl${headersString} -X POST -sSLd "${text}"`;
|
||||||
}
|
}
|
||||||
return `curl -sSL "${this.hostname}${text}"`;
|
return `curl${headersString} -sSL "${this.hostname}${text}"`;
|
||||||
}
|
}
|
||||||
if (this.method === 'POST') {
|
if (this.method === 'POST') {
|
||||||
return `curl -X POST -sSLd "${text}"`;
|
return `curl${headersString} -X POST -sSLd "${text}"`;
|
||||||
}
|
}
|
||||||
return `curl -sSL "${this.hostname}/${this.network}${text}"`;
|
return `curl${headersString} -sSL "${this.hostname}/${this.network}${text}"`;
|
||||||
} else if (this.env.BASE_MODULE === 'liquid') {
|
} else if (this.env.BASE_MODULE === 'liquid') {
|
||||||
if (this.method === 'POST') {
|
if (this.method === 'POST') {
|
||||||
if (this.network !== 'liquid') {
|
if (this.network !== 'liquid') {
|
||||||
text = text.replace('/api', `/${this.network}/api`);
|
text = text.replace('/api', `/${this.network}/api`);
|
||||||
}
|
}
|
||||||
return `curl -X POST -sSLd "${text}"`;
|
return `curl${headersString} -X POST -sSLd "${text}"`;
|
||||||
}
|
}
|
||||||
return ( this.network === 'liquid' ? `curl -sSL "${this.hostname}${text}"` : `curl -sSL "${this.hostname}/${this.network}${text}"` );
|
return ( this.network === 'liquid' ? `curl${headersString} -sSL "${this.hostname}${text}"` : `curl${headersString} -sSL "${this.hostname}/${this.network}${text}"` );
|
||||||
} else {
|
} else {
|
||||||
return `curl -sSL "${this.hostname}${text}"`;
|
return `curl${headersString} -sSL "${this.hostname}${text}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,9 +1,10 @@
|
|||||||
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { MenuGroup } from '../interfaces/services.interface';
|
import { MenuGroup } from '../interfaces/services.interface';
|
||||||
import { Observable, of, ReplaySubject, tap, catchError, share } from 'rxjs';
|
import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap } from 'rxjs';
|
||||||
import { IBackendInfo } from '../interfaces/websocket.interface';
|
import { IBackendInfo } from '../interfaces/websocket.interface';
|
||||||
import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
|
import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
|
||||||
|
|
||||||
@ -30,16 +31,20 @@ const SERVICES_API_PREFIX = `/api/v1/services`;
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ServicesApiServices {
|
export class ServicesApiServices {
|
||||||
private apiBaseUrl: string; // base URL is protocol, hostname, and port
|
apiBaseUrl: string; // base URL is protocol, hostname, and port
|
||||||
private apiBasePath: string; // network path is /testnet, etc. or '' for mainnet
|
apiBasePath: string; // network path is /testnet, etc. or '' for mainnet
|
||||||
|
|
||||||
userSubject$ = new ReplaySubject<IUser | null>(1);
|
userSubject$ = new ReplaySubject<IUser | null>(1);
|
||||||
|
currentAuth = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private storageService: StorageService
|
private storageService: StorageService,
|
||||||
|
private router: Router,
|
||||||
) {
|
) {
|
||||||
|
this.currentAuth = localStorage.getItem('auth');
|
||||||
|
|
||||||
this.apiBaseUrl = ''; // use relative URL by default
|
this.apiBaseUrl = ''; // use relative URL by default
|
||||||
if (!stateService.isBrowser) { // except when inside AU SSR process
|
if (!stateService.isBrowser) { // except when inside AU SSR process
|
||||||
this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
|
this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
|
||||||
@ -59,6 +64,10 @@ export class ServicesApiServices {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.getUserInfo$().subscribe();
|
this.getUserInfo$().subscribe();
|
||||||
|
this.router.events.pipe(
|
||||||
|
filter((event) => event instanceof NavigationStart && this.currentAuth !== localStorage.getItem('auth')),
|
||||||
|
switchMap(() => this.getUserInfo$()),
|
||||||
|
).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user