Merge branch 'master' into nymkappa/hashrate-indexing-message

This commit is contained in:
softsimon
2023-12-17 21:31:59 +07:00
committed by GitHub
51 changed files with 1108 additions and 131 deletions

View File

@@ -406,27 +406,23 @@
<div class="copyright">
<div class="title">
Copyright &copy; 2019-2023<br>
The Mempool Open Source Project
Mempool Space K.K.<br>
and other shadowy super-coders
</div>
<p>
<a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a> is free software; you can redistribute it and/or modify it under the terms of (at your option) either:<br>
<a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a> is free software; you can redistribute it and/or modify it under the terms of the <a href="https://www.gnu.org/licenses/agpl-3.0-standalone.html">GNU Affero General Public License</a> as published by the Free Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on &lt;https://mempool.space/about&gt;.<br>
</p>
<ul>
<li>
1) the <a href="https://www.gnu.org/licenses/agpl-3.0-standalone.html">GNU Affero General Public License</a> as published by the Free Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on &lt;https://mempool.space/about&gt;; or<br>
</li>
<li>
2) the <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU General Public License</a> as published by the Free Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on &lt;https://mempool.space/about&gt;.<br>
</li>
</ul>
<p>
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the full license terms for more details.<br>
</p>
<p>
This program incorporates software and other components licensed from third parties. See the full list of <a href="https://mempool.space/3rdpartylicenses.txt">Third-Party Licenses</a> for legal notices from those projects.
</p>
<div class="title">
Trademark Notice<br>
</div>
<p>
The Mempool Open Source Project&reg;, mempool.space&trade;, the mempool logo&reg;, the mempool.space logos&trade;, the mempool square logo&reg;, and the mempool blocks logo&trade; are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
The Mempool Open Source Project&reg;, Mempool Accelerator&trade;, Mempool Enterprise&reg;, Mempool Liquidity&trade;, mempool.space&reg;, Be your own explorer&trade;, Explore the full Bitcoin ecosystem&trade;, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
</p>
<p>
While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our <a href="https://mempool.space/trademark-policy">Trademark Policy and Guidelines</a> for more details, published on &lt;https://mempool.space/trademark-policy&gt;.

View File

@@ -25,7 +25,7 @@
></app-accelerate-fee-graph>
</ng-container>
<ng-container *ngIf="estimate">
<ng-container *ngIf="estimate else loadingEstimate">
<div [class]="{estimateDisabled: error}">
<div *ngIf="user && !estimate.hasAccess">
@@ -243,4 +243,9 @@
</div>
</ng-container>
</div>
</div>
<ng-template #loadingEstimate>
<div class="skeleton-loader"></div>
<br>
</ng-template>

View File

@@ -0,0 +1,33 @@
<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">
<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>
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
</a>
<div class="filter-bar">
<button class="menu-toggle" (click)="menuOpen = !menuOpen" title="Mempool Goggles">
<app-svg-images name="goggles" width="100%" height="100%"></app-svg-images>
</button>
<div class="active-tags">
<ng-container *ngFor="let filter of activeFilters;">
<button class="btn filter-tag selected" (click)="toggleFilter(filter)">{{ filters[filter].label }}</button>
</ng-container>
</div>
</div>
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
<ng-container *ngFor="let group of filterGroups;">
<h5>{{ group.label }}</h5>
<div class="filter-group">
<ng-container *ngFor="let filter of group.filters;">
<button class="btn filter-tag" [class.selected]="filterFlags[filter.key]" (click)="toggleFilter(filter.key)">{{ filter.label }}</button>
</ng-container>
</div>
</ng-container>
</div>
<div class="filter-menu" *ngIf="menuOpen && cssWidth <= 280">
<ng-container *ngFor="let group of filterGroups;">
<ng-container *ngFor="let filter of group.filters;">
<button *ngIf="filter.important" class="btn filter-tag" [class.selected]="filterFlags[filter.key]" (click)="toggleFilter(filter.key)">{{ filter.label }}</button>
</ng-container>
</ng-container>
</div>
</div>

View File

@@ -0,0 +1,142 @@
.block-filters {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 1em;
z-index: 10;
pointer-events: none;
.filter-bar, .active-tags {
display: flex;
flex-direction: row;
align-items: center;
}
.active-tags {
flex-wrap: wrap;
row-gap: 0.25em;
margin-left: 0.5em;
}
.info-badges {
display: flex;
flex-direction: row;
align-items: center;
float: right;
&:hover, &:active {
text-decoration: none;
}
}
.menu-toggle {
width: 2em;
height: 2em;
padding: 0px 1px;
opacity: 0;
cursor: pointer;
color: white;
background: none;
border: solid 2px white;
border-radius: 0.35em;
pointer-events: all;
}
.filter-menu {
h5 {
font-size: 0.8rem;
color: white;
margin: 0;
margin-top: 0.5em;
}
}
.filter-group {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 0.25em;
margin-bottom: 0.5em;
}
.filter-tag {
font-size: 0.9em;
background: #181b2daf;
border: solid 1px #105fb0;
color: white;
border-radius: 0.2rem;
padding: 0.2em 0.5em;
transition: background-color 300ms;
margin-right: 0.25em;
pointer-events: all;
&.selected {
background-color: #105fb0;
}
}
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
.menu-toggle {
opacity: 0.5;
background: #181b2d;
&:hover {
opacity: 1;
background: #181b2d7f;
}
}
&.menu-open, &.filters-active {
.menu-toggle {
opacity: 1;
background: none;
&:hover {
background: #181b2d7f;
}
}
}
}
&.menu-open, &.filters-active {
.menu-toggle {
opacity: 1;
background: none;
&:hover {
background: #181b2d7f;
}
}
}
&.menu-open {
pointer-events: all;
background: #181b2d7f;
}
&.small {
.filter-tag {
font-size: 0.8em;
}
}
&.vsmall {
.filter-menu {
margin-top: 0.25em;
h5 {
display: none;
}
}
.filter-tag {
font-size: 0.7em;
}
}
&.tiny {
.filter-tag {
font-size: 0.5em;
}
}
}

View File

@@ -0,0 +1,70 @@
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core';
import { FilterGroups, TransactionFilters } from '../../shared/filters.utils';
@Component({
selector: 'app-block-filters',
templateUrl: './block-filters.component.html',
styleUrls: ['./block-filters.component.scss'],
})
export class BlockFiltersComponent implements OnChanges {
@Input() cssWidth: number = 800;
@Output() onFilterChanged: EventEmitter<bigint | null> = new EventEmitter();
filters = TransactionFilters;
filterGroups = FilterGroups;
activeFilters: string[] = [];
filterFlags: { [key: string]: boolean } = {};
menuOpen: boolean = false;
constructor(
private cd: ChangeDetectorRef,
) {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.cssWidth) {
this.cd.markForCheck();
}
}
toggleFilter(key): void {
const filter = this.filters[key];
this.filterFlags[key] = !this.filterFlags[key];
if (this.filterFlags[key]) {
// remove any other flags in the same toggle group
if (filter.toggle) {
this.activeFilters.forEach(f => {
if (this.filters[f].toggle === filter.toggle) {
this.filterFlags[f] = false;
}
});
this.activeFilters = this.activeFilters.filter(f => this.filters[f].toggle !== filter.toggle);
}
// add new active filter
this.activeFilters.push(key);
} else {
// remove active filter
this.activeFilters = this.activeFilters.filter(f => f != key);
}
this.onFilterChanged.emit(this.getBooleanFlags());
}
getBooleanFlags(): bigint | null {
let flags = 0n;
for (const key of Object.keys(this.filterFlags)) {
if (this.filterFlags[key]) {
flags |= this.filters[key].flag;
}
}
return flags || null;
}
@HostListener('document:click', ['$event'])
onClick(event): boolean {
// click away from menu
if (!event.target.closest('button')) {
this.menuOpen = false;
}
return true;
}
}

View File

@@ -13,5 +13,6 @@
[auditEnabled]="auditHighlighting"
[blockConversion]="blockConversion"
></app-block-overview-tooltip>
<app-block-filters *ngIf="showFilters" [cssWidth]="cssWidth" (onFilterChanged)="setFilterFlags($event)"></app-block-filters>
</div>
</div>

View File

@@ -8,6 +8,19 @@ import { Color, Position } from './sprite-types';
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';
const unmatchedOpacity = 0.2;
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
const unmatchedAuditFeeColors = defaultAuditFeeColors.map(c => setOpacity(c, unmatchedOpacity));
const unmatchedMarginalFeeColors = defaultMarginalFeeColors.map(c => setOpacity(c, unmatchedOpacity));
const unmatchedAuditColors = {
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
added: setOpacity(defaultAuditColors.added, unmatchedOpacity),
selected: setOpacity(defaultAuditColors.selected, unmatchedOpacity),
accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity),
};
@Component({
selector: 'app-block-overview-graph',
@@ -26,6 +39,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@Input() mirrorTxid: string | void;
@Input() unavailable: boolean = false;
@Input() auditHighlighting: boolean = false;
@Input() showFilters: boolean = false;
@Input() filterFlags: bigint | null = null;
@Input() blockConversion: Price;
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
@@ -92,9 +107,21 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
if (changes.auditHighlighting) {
this.setHighlightingEnabled(this.auditHighlighting);
}
if (changes.overrideColor) {
if (changes.overrideColor && this.scene) {
this.scene.setColorFunction(this.overrideColors);
}
if ((changes.filterFlags || changes.showFilters) && this.scene) {
this.setFilterFlags(this.filterFlags);
}
}
setFilterFlags(flags: bigint | null): void {
if (flags != null) {
this.scene.setColorFunction(this.getFilterColorFunction(flags));
} else {
this.scene.setColorFunction(this.overrideColors);
}
this.start();
}
ngOnDestroy(): void {
@@ -374,6 +401,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
onPointerMove(event) {
if (event.target === this.canvas.nativeElement) {
this.setPreviewTx(event.offsetX, event.offsetY, false);
} else {
this.onPointerLeave(event);
}
}
@@ -474,6 +503,22 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
onTxHover(hoverId: string) {
this.txHoverEvent.emit(hoverId);
}
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
return (tx: TxView) => {
if ((tx.bigintFlags & flags) === flags) {
return defaultColorFunction(tx);
} else {
return defaultColorFunction(
tx,
unmatchedFeeColors,
unmatchedAuditFeeColors,
unmatchedMarginalFeeColors,
unmatchedAuditColors
);
}
};
}
}
// WebGL shader attributes

View File

@@ -2,19 +2,7 @@ import { FastVertexArray } from './fast-vertex-array';
import TxView from './tx-view';
import { TransactionStripped } from '../../interfaces/websocket.interface';
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
import { feeLevels, mempoolFeeColors } from '../../app.constants';
import { darken, desaturate, hexToColor } from './utils';
const feeColors = mempoolFeeColors.map(hexToColor);
const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
const marginalFeeColors = feeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
const auditColors = {
censored: hexToColor('f344df'),
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
added: hexToColor('0099ff'),
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
accelerated: hexToColor('8F5FF6'),
};
import { defaultColorFunction } from './utils';
export default class BlockScene {
scene: { count: number, offset: { x: number, y: number}};
@@ -27,6 +15,7 @@ export default class BlockScene {
configAnimationOffset: number | null;
animationOffset: number;
highlightingEnabled: boolean;
filterFlags: bigint | null = 0b00000100_00000000_00000000_00000000n;
width: number;
height: number;
gridWidth: number;
@@ -78,7 +67,7 @@ export default class BlockScene {
}
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
this.getColor = colorFunction;
this.getColor = colorFunction || defaultColorFunction;
this.dirty = true;
if (this.initialised && this.scene) {
this.updateColors(performance.now(), 50);
@@ -277,6 +266,20 @@ export default class BlockScene {
this.animateUntil = Math.max(this.animateUntil, tx.update(update));
}
private updateTxColor(tx: TxView, startTime: number, delay: number, animate: boolean = true, duration?: number): void {
if (tx.dirty || this.dirty) {
const txColor = this.getColor(tx);
this.applyTxUpdate(tx, {
display: {
color: txColor
},
duration: animate ? (duration || this.animationDuration) : 1,
start: startTime,
delay: animate ? delay : 0,
});
}
}
private updateTx(tx: TxView, startTime: number, delay: number, direction: string = 'left', animate: boolean = true): void {
if (tx.dirty || this.dirty) {
this.saveGridToScreenPosition(tx);
@@ -325,7 +328,7 @@ export default class BlockScene {
} else {
this.applyTxUpdate(tx, {
display: {
position: tx.screenPosition
position: tx.screenPosition,
},
duration: animate ? this.animationDuration : 0,
minDuration: animate ? (this.animationDuration / 2) : 0,
@@ -903,49 +906,4 @@ class BlockLayout {
function feeRateDescending(a: TxView, b: TxView) {
return b.feerate - a.feerate;
}
function defaultColorFunction(tx: TxView): Color {
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
// Normal mode
if (!tx.scene?.highlightingEnabled) {
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
return feeLevelColor;
}
// Block audit
switch(tx.status) {
case 'censored':
return auditColors.censored;
case 'missing':
case 'sigop':
case 'rbf':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'fresh':
case 'freshcpfp':
return auditColors.missing;
case 'added':
return auditColors.added;
case 'selected':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'accelerated':
return auditColors.accelerated;
case 'found':
if (tx.context === 'projected') {
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
} else {
return feeLevelColor;
}
default:
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
}
}

View File

@@ -1,9 +1,9 @@
import TxSprite from './tx-sprite';
import { FastVertexArray } from './fast-vertex-array';
import { TransactionStripped } from '../../interfaces/websocket.interface';
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
import { hexToColor } from './utils';
import BlockScene from './block-scene';
import { TransactionStripped } from '../../interfaces/node-api.interface';
const hoverTransitionTime = 300;
const defaultHoverColor = hexToColor('1bd8f4');
@@ -29,6 +29,7 @@ export default class TxView implements TransactionStripped {
feerate: number;
acc?: boolean;
rate?: number;
bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n;
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated';
context?: 'projected' | 'actual';
scene?: BlockScene;
@@ -57,6 +58,7 @@ export default class TxView implements TransactionStripped {
this.acc = tx.acc;
this.rate = tx.rate;
this.status = tx.status;
this.bigintFlags = tx.flags ? BigInt(tx.flags) : 0n;
this.initialised = false;
this.vertexArray = scene.vertexArray;

View File

@@ -1,4 +1,6 @@
import { feeLevels, mempoolFeeColors } from '../../app.constants';
import { Color } from './sprite-types';
import TxView from './tx-view';
export function hexToColor(hex: string): Color {
return {
@@ -25,5 +27,75 @@ export function darken(color: Color, amount: number): Color {
g: color.g * amount,
b: color.b * amount,
a: color.a,
};
}
export function setOpacity(color: Color, opacity: number): Color {
return {
...color,
a: opacity
};
}
// precomputed colors
export const defaultFeeColors = mempoolFeeColors.map(hexToColor);
export const defaultAuditFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
export const defaultMarginalFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
export const defaultAuditColors = {
censored: hexToColor('f344df'),
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
added: hexToColor('0099ff'),
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
accelerated: hexToColor('8F5FF6'),
};
export function defaultColorFunction(
tx: TxView,
feeColors: Color[] = defaultFeeColors,
auditFeeColors: Color[] = defaultAuditFeeColors,
marginalFeeColors: Color[] = defaultMarginalFeeColors,
auditColors: { [status: string]: Color } = defaultAuditColors
): Color {
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
// Normal mode
if (!tx.scene?.highlightingEnabled) {
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
return feeLevelColor;
}
// Block audit
switch(tx.status) {
case 'censored':
return auditColors.censored;
case 'missing':
case 'sigop':
case 'rbf':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'fresh':
case 'freshcpfp':
return auditColors.missing;
case 'added':
return auditColors.added;
case 'selected':
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
case 'accelerated':
return auditColors.accelerated;
case 'found':
if (tx.context === 'projected') {
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
} else {
return feeLevelColor;
}
default:
if (tx.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
}
}

View File

@@ -1,7 +1,7 @@
import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { TransactionStripped } from '../../interfaces/websocket.interface';
import { Position } from '../../components/block-overview-graph/sprite-types.js';
import { Price } from '../../services/price.service';
import { TransactionStripped } from '../../interfaces/node-api.interface.js';
@Component({
selector: 'app-block-overview-tooltip',

View File

@@ -42,7 +42,7 @@
<ng-container *ngIf="!isLoadingBlock; else skeletonRows">
<tr>
<td class="td-width" i18n="block.hash">Hash</td>
<td>&lrm;<a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
<td>&lrm;<a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard [text]="block.id"></app-clipboard></td>
</tr>
<tr>
<td i18n="block.timestamp">Timestamp</td>

View File

@@ -5,6 +5,7 @@
[blockLimit]="stateService.blockVSize"
[orientation]="timeLtr ? 'right' : 'left'"
[flip]="true"
[showFilters]="showFilters"
[overrideColors]="overrideColors"
(txClickEvent)="onTxClick($event)"
></app-block-overview-graph>

View File

@@ -18,6 +18,7 @@ import TxView from '../block-overview-graph/tx-view';
})
export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
@Input() index: number;
@Input() showFilters: boolean = false;
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
@Output() txPreviewEvent = new EventEmitter<TransactionStripped | void>();

View File

@@ -1,5 +1,5 @@
<div class="block-wrapper">
<div class="block-container">
<app-mempool-block-overview [index]="index"></app-mempool-block-overview>
<app-mempool-block-overview [index]="index" [showFilters]="true"></app-mempool-block-overview>
</div>
</div>

View File

@@ -27,6 +27,7 @@ export class MempoolBlockViewComponent implements OnInit, OnDestroy {
autofit: boolean = false;
resolution: number = 80;
index: number = 0;
filterFlags: bigint | null = 0n;
routeParamsSubscription: Subscription;
queryParamsSubscription: Subscription;
@@ -38,6 +39,8 @@ export class MempoolBlockViewComponent implements OnInit, OnDestroy {
) { }
ngOnInit(): void {
window['setFlags'] = this.setFilterFlags.bind(this);
this.websocketService.want(['blocks', 'mempool-blocks']);
this.routeParamsSubscription = this.route.paramMap
@@ -82,4 +85,8 @@ export class MempoolBlockViewComponent implements OnInit, OnDestroy {
this.routeParamsSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe();
}
setFilterFlags(flags: bigint | null) {
this.filterFlags = flags;
}
}

View File

@@ -46,7 +46,9 @@
<app-fee-distribution-graph *ngIf="webGlEnabled" [transactions]="mempoolBlockTransactions$ | async" [feeRange]="mempoolBlock.isStack ? mempoolBlock.feeRange : []" [vsize]="mempoolBlock.blockVSize" ></app-fee-distribution-graph>
</div>
<div class="col-md chart-container">
<app-mempool-block-overview *ngIf="webGlEnabled" [index]="mempoolBlockIndex" (txPreviewEvent)="setTxPreview($event)"></app-mempool-block-overview>
<div class="block-with-filters" *ngIf="webGlEnabled">
<app-mempool-block-overview [index]="mempoolBlockIndex" (txPreviewEvent)="setTxPreview($event)" [showFilters]="true"></app-mempool-block-overview>
</div>
<app-fee-distribution-graph *ngIf="!webGlEnabled" [transactions]="mempoolBlockTransactions$ | async" [feeRange]="mempoolBlock.isStack ? mempoolBlock.feeRange : []" [vsize]="mempoolBlock.blockVSize" ></app-fee-distribution-graph>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { StateService } from '../../services/state.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap, map, tap, filter } from 'rxjs/operators';
@@ -28,6 +28,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
public stateService: StateService,
private seoService: SeoService,
private websocketService: WebsocketService,
private cd: ChangeDetectorRef,
) {
this.webGlEnabled = detectWebGL();
}

View File

@@ -174,7 +174,7 @@ export class SearchFormComponent implements OnInit {
const addressPrefixSearchResults = result[0];
const lightningResults = result[1];
const matchesBlockHeight = this.regexBlockheight.test(searchText);
const matchesBlockHeight = this.regexBlockheight.test(searchText) && parseInt(searchText) <= this.stateService.latestBlockHeight;
const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date';
const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText);
const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText);
@@ -217,7 +217,7 @@ export class SearchFormComponent implements OnInit {
selectedResult(result: any): void {
if (typeof result === 'string') {
this.search(result);
} else if (typeof result === 'number') {
} else if (typeof result === 'number' && result <= this.stateService.latestBlockHeight) {
this.navigate('/block/', result.toString());
} else if (result.alias) {
this.navigate('/lightning/node/', result.public_key);
@@ -232,8 +232,10 @@ export class SearchFormComponent implements OnInit {
this.isSearching = true;
if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) {
this.navigate('/address/', searchText);
} else if (this.regexBlockhash.test(searchText) || this.regexBlockheight.test(searchText)) {
} else if (this.regexBlockhash.test(searchText)) {
this.navigate('/block/', searchText);
} else if (this.regexBlockheight.test(searchText)) {
parseInt(searchText) <= this.stateService.latestBlockHeight ? this.navigate('/block/', searchText) : this.isSearching = false;
} else if (this.regexTransaction.test(searchText)) {
const matches = this.regexTransaction.exec(searchText);
if (this.network === 'liquid' || this.network === 'liquidtestnet') {

View File

@@ -84,6 +84,14 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M9 9.5v-2a3 3 0 116 0v2c0 1.11-.603 2.08-1.5 2.599v1.224a1 1 0 00.629.928l2.05.82A3.693 3.693 0 0118.5 18.5h-13c0-1.51.92-2.868 2.321-3.428l2.05-.82a1 1 0 00.629-.929v-1.224A2.999 2.999 0 019 9.5z"></path>
</svg>
</ng-container>
<ng-container *ngSwitchCase="'goggles'">
<svg viewBox="0 0 558.56415 255.62396" [attr.width]="width" [attr.height]="height" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="m 466.62029,0.15290693 c 2.84,0 5.90333,0.296667 9.19,0.88999997 17.05333,3.066667 31.92333,10.4666671 44.61,22.2000001 15.02,13.88 24.94,35.04 25.46,54.77 0.0133,0.54 0.26,0.93 0.74,1.17 5.05,2.52 9.14,6.28 10.82,11.39 0.79333,2.38667 1.16667,6.12 1.12,11.200003 -0.14,15.94667 -0.19,30.22667 -0.15,42.84 0.03,8.92 -3.88,14.6 -11.36,19.02 -0.71869,0.42495 -1.17676,1.1834 -1.22,2.02 -0.33,6.26 -0.72,13.09 -2.3,19.16 -2.2,8.49 -5.1,16.06 -9.74,23.78 -1.71333,2.85333 -3.41333,5.77333 -5.1,8.76 -2.64,4.68 -4.99,8.03667 -7.05,10.07 -2.45,2.43 -4.45,5.13 -7.02,7.38 -11.13,9.75 -26.36,16.58 -40.92,19.39 -5.05333,0.98 -11.52667,1.45667 -19.42,1.43 -25.96,-0.0667 -51.90333,-0.12 -77.83,-0.16 -10.08,-0.01 -20.45,-1.66 -29.9,-6 -15.07333,-6.92 -26.44667,-17.19667 -34.12,-30.83 -3.17,-5.64 -5.15,-11.78 -8.42,-17.3 -4.07333,-6.87333 -10.09,-11.46 -18.05,-13.76 -12.38,-3.57 -25.31,2.57 -32.13,13.15 -2.71,4.19 -4.3,9.04 -6.77,13.41 -1.99,3.52 -3.53,7.35 -5.7,10.78 -3.36667,5.34667 -5.54,8.46667 -6.52,9.36 -1.70667,1.56667 -3.5,3.16333 -5.38,4.79 -7.94,6.88 -18.64,11.02 -30.11,14.12 -5.35333,1.44667 -10.01667,2.18333 -13.99,2.21 -17.9,0.13333 -42.16667,0.17667 -72.8,0.13 -6.71333,-0.007 -13.376669,-0.14667 -19.990002,-0.42 -9.086667,-0.36667 -18.55,-2.88667 -28.39,-7.56 -18.233333,-8.65333 -32.26,-22.45333 -42.08,-41.4 -6.913333,-13.34667 -9.993333,-26.95 -9.24,-40.81 0.03144,-0.59686 -0.306835,-1.15004 -0.85,-1.39 -8.8400001,-3.98 -12.00000011,-10.78 -11.96000011,-20.36 0.05333333,-16.54667 0.03666667,-32.38 -0.0500000043,-47.500003 -0.0399999957,-7.54 4.09000001427,-13.94 11.34000011427,-16.48 0.543871,-0.19451 0.915559,-0.69668 0.94,-1.27 1.78,-39.54 32.94,-72.5000001 71.86,-77.57000007 4.18,-0.54 10.406667,-0.793333 18.680002,-0.76 363.8,0.15 0,0 363.8,0.15 z m 1.98,216.71000307 c 11.01,-2.05 20.88,-8.4 27.78,-17.23 7.51,-9.63 13.42,-21.1 13.47,-33.01 0.12,-25.71333 0.21667,-52.20667 0.29,-79.480003 0.0133,-6.34 -0.47333,-11.32 -1.46,-14.94 -3,-11 -9.03667,-19.66333 -18.11,-25.99 -7.32,-5.113333 -14.88667,-7.693333 -22.7,-7.74 -41.5,-0.233333 -85.66667,-0.276667 -132.5,-0.13 -62.12,0.206667 -142.78667,0.23 -242.000002,0.07 -12.52,-0.02 -23.406667,4.326667 -32.66,13.04 -7.79,7.34 -12.17,18.06 -12.13,28.98 0.09333,27.086673 0.06,54.470003 -0.1,82.150003 -0.03333,5.97333 0.393333,10.85333 1.28,14.64 4.28,18.33 19.71,34.9 37.96,39.18 3.7,0.86667 8.936667,1.27333 15.710002,1.22 24.08667,-0.18667 50.07,-0.19 77.95,-0.01 2.99333,0.02 5.98667,-0.41 8.98,-1.29 16.42,-4.84 24.52,-15.8 31.08,-30.65 1.76667,-3.99333 4.16333,-8.27 7.19,-12.83 7.95,-11.98 19.14,-18.59 32.66,-22.45 6.32667,-1.80667 11.84667,-2.70333 16.56,-2.69 25.87,0.07 47.99,12.39 58.26,35.87 1.54,3.52667 3.25333,7.07333 5.14,10.64 6.20667,11.73333 15.25333,19.07333 27.14,22.02 3.79333,0.94667 10.03667,1.39333 18.73,1.34 23.82667,-0.13333 47.73,-0.13667 71.71,-0.01 3.92,0.02 7.17667,-0.21333 9.77,-0.7 z" id="outline" />
<path fill="currentColor" opacity="0.3" d="m 496.97029,199.03291 c -6.9,8.83 -16.77,15.18 -27.78,17.23 q -3.89,0.73 -9.77,0.7 -35.97,-0.19 -71.71,0.01 -13.04,0.08 -18.73,-1.34 -17.83,-4.42 -27.14,-22.02 -2.83,-5.35 -5.14,-10.64 c -10.27,-23.48 -32.39,-35.8 -58.26,-35.87 q -7.07,-0.02 -16.56,2.69 c -13.52,3.86 -24.71,10.47 -32.66,22.45 q -4.54,6.84 -7.19,12.83 c -6.56,14.85 -14.66,25.81 -31.08,30.65 q -4.49,1.32 -8.98,1.29 -41.82,-0.27 -77.95,0.01 -10.160002,0.08 -15.710002,-1.22 c -18.25,-4.28 -33.68,-20.85 -37.96,-39.18 q -1.33,-5.68 -1.28,-14.64 0.24,-41.52 0.1,-82.150003 c -0.04,-10.92 4.34,-21.64 12.13,-28.98 q 13.88,-13.07 32.66,-13.04 148.820002,0.24 242.000002,-0.07 70.25,-0.22 132.5,0.13 11.72,0.07 22.7,7.74 13.61,9.49 18.11,25.99 1.48,5.43 1.46,14.94 -0.11,40.910003 -0.29,79.480003 c -0.05,11.91 -5.96,23.38 -13.47,33.01 z m -8.14,-101.340003 c 5.11,-2.24 9.54,-9.21 6.39,-14.8 q -1.59,-2.82 -4.29,-5.41 -8.04,-7.73 -15.91,-15.96 -2.88,-3.02 -5.51,-4.19 c -6.41,-2.84 -13.19,1.02 -15.6,7.25 -1.35,3.51 0.64,7.36 3.07,9.77 q 9.48,9.38 20.18,20.59 5.3,5.550003 11.67,2.75 z m -404.320002,-6.02 c 4.77,-6.16 10.61,-11.82 16.350002,-17.36 q 4.6,-4.45 3.41,-9.38 c -1.57,-6.47 -9.240002,-9.94 -15.220002,-7.57 q -2.72,1.07 -7.3,5.87 -7.54,7.9 -15.2,15.77 -2.83,2.9 -3.54,6.23 c -1.67,7.85 5.38,14.06 12.94,13.26 3.57,-0.39 6.5,-4.17 8.56,-6.82 z" id="lens" />
<path fill="currentColor" d="m 488.83029,97.692907 q -6.37,2.800003 -11.67,-2.75 -10.7,-11.21 -20.18,-20.59 c -2.43,-2.41 -4.42,-6.26 -3.07,-9.77 2.41,-6.23 9.19,-10.09 15.6,-7.25 q 2.63,1.17 5.51,4.19 7.87,8.23 15.91,15.96 2.7,2.59 4.29,5.41 c 3.15,5.59 -1.28,12.56 -6.39,14.8 z" id="glint-a" />
<path fill="currentColor" d="m 84.510288,91.672907 c -2.06,2.65 -4.99,6.43 -8.56,6.82 -7.56,0.8 -14.61,-5.41 -12.94,-13.26 q 0.71,-3.33 3.54,-6.23 7.66,-7.87 15.2,-15.77 4.58,-4.8 7.3,-5.87 c 5.98,-2.37 13.650002,1.1 15.220002,7.57 q 1.19,4.93 -3.41,9.38 c -5.740002,5.54 -11.580002,11.2 -16.350002,17.36 z" id="glint-b" />
</svg>
</ng-container>
</ng-container>
<ng-template #bitcoinLogo let-color let-width="width" let-height="height" let-viewBox="viewBox">

View File

@@ -8,7 +8,7 @@
<div *ngIf="officialMempoolSpace">
<h2>Trademark Policy and Guidelines</h2>
<h5>The Mempool Open Source Project &reg;</h5>
<h6>Updated: July 19, 2021</h6>
<h6>Updated: December 7, 2023</h6>
<br>
<div class="text-left">
@@ -56,7 +56,12 @@
<tbody>
<tr><td>Mempool Space K.K.</td></tr>
<tr><td>The Mempool Open Source Project</td></tr>
<tr><td>Mempool Accelerator</td></tr>
<tr><td>Mempool Enterprise</td></tr>
<tr><td>Mempool Liquidity</td></tr>
<tr><td>mempool.space</td></tr>
<tr><td>Be your own explorer</td></tr>
<tr><td>Explore the full Bitcoin ecosystem</td></tr>
</tbody>
</table>
</div>
@@ -89,11 +94,16 @@
<p>The mempool Square Logo</p>
<br><br>
<img src="/resources/mempool-blocks.png" style="width: 500px; max-width: 80%">
<img src="/resources/mempool-blocks-2-3-logo.jpeg" style="width: 500px; max-width: 80%">
<br><br>
<p>The mempool Blocks Logo</p>
<br><br>
<img src="/resources/mempool-blocks-3-2-logo.jpeg" style="width: 500px; max-width: 80%">
<br><br>
<p>The mempool Blocks 3 | 2 Logo</p>
<br><br>
</div>
<br>
@@ -304,8 +314,7 @@
<p>Also, if you are using our Marks in a way described in the sections "Uses for Which We Are Granting a License," you must include the following trademark attribution at the foot of the webpage where you have used the Mark (or, if in a book, on the credits page), on any packaging or labeling, and on advertising or marketing materials:</p>
<p>The Mempool Space K.K.&trade;, The Mempool Open Source Project&reg;, mempool.space&trade;, the mempool logo&reg;, the mempool.space logos&trade;, the mempool square logo&reg;, and the mempool blocks logo&trade; are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein.</p>
<p>"The Mempool Open Source Project&reg;, Mempool Accelerator&trade;, Mempool Enterprise&reg;, Mempool Liquidity&trade;, mempool.space&reg;, Be your own explorer&trade;, Explore the full Bitcoin ecosystem&trade;, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."</p>
<li>What to Do When You See Abuse</li>
<br>