Merge branch 'master' into natsoni/more-fiat-currencies

This commit is contained in:
natsoni 2024-03-09 11:53:07 +01:00 committed by GitHub
commit 23076172e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 509 additions and 231 deletions

View File

@ -4027,9 +4027,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
@ -10683,9 +10683,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
},
"ipaddr.js": {
"version": "1.9.1",

View File

@ -56,6 +56,9 @@ class AccelerationRepository {
}
public async $getAccelerationInfo(poolSlug: string | null = null, height: number | null = null, interval: string | null = null): Promise<PublicAcceleration[]> {
if (!interval || !['24h', '3d', '1w', '1m'].includes(interval)) {
interval = '1m';
}
interval = Common.getSqlInterval(interval);
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || (interval == null && poolSlug == null && height == null)) {

View File

@ -8070,13 +8070,18 @@
}
},
"node_modules/es5-ext": {
"version": "0.10.53",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
"integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
"version": "0.10.64",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.3",
"next-tick": "~1.0.0"
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
@ -8624,6 +8629,25 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/esniff/node_modules/type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
},
"node_modules/espree": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
@ -10397,9 +10421,9 @@
}
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
},
"node_modules/ipaddr.js": {
"version": "2.1.0",
@ -12508,9 +12532,9 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"node_modules/next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/ngx-echarts": {
"version": "16.2.0",
@ -23055,13 +23079,14 @@
}
},
"es5-ext": {
"version": "0.10.53",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
"integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
"version": "0.10.64",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"requires": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.3",
"next-tick": "~1.0.0"
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
}
},
"es6-iterator": {
@ -23472,6 +23497,24 @@
"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
"dev": true
},
"esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"requires": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"dependencies": {
"type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
}
}
},
"espree": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
@ -24802,9 +24845,9 @@
}
},
"ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
},
"ipaddr.js": {
"version": "2.1.0",
@ -26374,9 +26417,9 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"ngx-echarts": {
"version": "16.2.0",

View File

@ -9,23 +9,38 @@
</button>
</div>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="daysAvailable">
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 24H
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3d'">
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 1" [class.active]="radioGroupForm.get('dateSpan').value === '3d'">
<input type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 3D
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1w'">
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 3" [class.active]="radioGroupForm.get('dateSpan').value === '1w'">
<input type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 1W
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 7" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 1M
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 30" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 3M
</label>
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 90" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 6M
</label>
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 180" [class.active]="radioGroupForm.get('dateSpan').value === '1y'">
<input type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 1Y
</label>
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 360" [class.active]="radioGroupForm.get('dateSpan').value === '2y'">
<input type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 2Y
</label>
<label class="btn btn-primary btn-sm" *ngIf="daysAvailable >= 720" [class.active]="radioGroupForm.get('dateSpan').value === '3y'">
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 3Y
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> ALL
</label>
</div>
</form>
</div>

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
import { EChartsOption } from 'echarts';
import { Observable, Subscription, combineLatest, fromEvent } from 'rxjs';
import { Observable, Subscription, combineLatest, fromEvent, share } from 'rxjs';
import { startWith, switchMap, tap } from 'rxjs/operators';
import { SeoService } from '../../../services/seo.service';
import { formatNumber } from '@angular/common';
@ -41,8 +41,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
renderer: 'svg',
};
hrStatsObservable$: Observable<any>;
statsObservable$: Observable<any>;
aggregatedHistory$: Observable<any>;
statsSubscription: Subscription;
isLoading = true;
formatNumber = formatNumber;
@ -50,6 +49,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
chartInstance: any = undefined;
currency: string;
daysAvailable: number = 0;
constructor(
@Inject(LOCALE_ID) public locale: string,
@ -81,7 +81,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
}
});
this.statsObservable$ = combineLatest([
this.aggregatedHistory$ = combineLatest([
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
startWith(this.radioGroupForm.controls.dateSpan.value),
switchMap((timespan) => {
@ -95,14 +95,17 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
),
fromEvent(window, 'resize').pipe(startWith(null)),
]).pipe(
tap(([history]) => {
tap(([response]) => {
const history: Acceleration[] = response.body;
this.daysAvailable = (new Date().getTime() / 1000 - response.headers.get('x-oldest-accel')) / (24 * 3600);
this.isLoading = false;
this.prepareChartOptions(history);
this.cd.markForCheck();
})
}),
share(),
);
this.statsObservable$.subscribe();
this.aggregatedHistory$.subscribe();
}
prepareChartOptions(data) {
@ -247,6 +250,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
type: 'bar',
barWidth: '90%',
large: true,
barMinHeight: 1,
},
],
dataZoom: (this.widget || data.length === 0 )? undefined : [{

View File

@ -44,7 +44,7 @@ export class AccelerationsListComponent implements OnInit {
this.accelerationList$ = this.pageSubject.pipe(
switchMap((page) => {
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistoryObserveResponse$({ timeframe: '1y', page: page }));
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistoryObserveResponse$({ page: page }));
return accelerationObservable$.pipe(
switchMap(response => {
let accelerations = response;

View File

@ -60,7 +60,7 @@ export class AcceleratorDashboardComponent implements OnInit {
this.accelerations$ = this.stateService.chainTip$.pipe(
distinctUntilChanged(),
switchMap(() => {
return this.serviceApiServices.getAccelerationHistory$({ timeframe: '3m', page: 1, pageLength: 100}).pipe(
return this.serviceApiServices.getAccelerationHistory$({}).pipe(
catchError(() => {
return of([]);
}),

View File

@ -3,7 +3,7 @@
<div class="block-overview-graph">
<canvas class="block-overview-canvas" [class.clickable]="!!hoverTx" #blockCanvas></canvas>
<div class="loader-wrapper" [class.hidden]="(!isLoading || disableSpinner) && !unavailable">
<div *ngIf="isLoading" class="spinner-border ml-3 loading" role="status"></div>
<div *ngIf="!unavailable" class="spinner-border ml-3 loading" role="status"></div>
<div *ngIf="!isLoading && unavailable" class="ml-3" i18n="block.not-available">not available</div>
</div>
<app-block-overview-tooltip

View File

@ -60,6 +60,5 @@
&.hidden {
opacity: 0;
transition: opacity 500ms;
}
}

View File

@ -433,6 +433,9 @@
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
<td>
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
<span *ngIf="oobFees" class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band">
+<app-amount [satoshis]="oobFees" digitsInfo="1.2-4" [noFiat]="true"></app-amount>
</span>
<span *ngIf="blockAudit.feeDelta" class="difference" [class.positive]="blockAudit.feeDelta <= 0" [class.negative]="blockAudit.feeDelta > 0">
{{ blockAudit.feeDelta < 0 ? '+' : '' }}{{ (-blockAudit.feeDelta * 100) | amountShortener: 2 }}%
</span>

View File

@ -288,6 +288,10 @@ h1 {
@media (max-width: 767.98px) {
margin-top: 0.75rem;
}
.oobFees {
color: #653b9c;
}
}
.graph-col {

View File

@ -9,7 +9,7 @@ import { StateService } from '../../services/state.service';
import { SeoService } from '../../services/seo.service';
import { WebsocketService } from '../../services/websocket.service';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
import { AccelerationInfo, BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
import { ApiService } from '../../services/api.service';
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
import { detectWebGL } from '../../shared/graphs.utils';
@ -43,6 +43,7 @@ export class BlockComponent implements OnInit, OnDestroy {
latestBlock: BlockExtended;
latestBlocks: BlockExtended[] = [];
transactions: Transaction[];
oobFees: number = 0;
isLoadingTransactions = true;
strippedTransactions: TransactionStripped[];
overviewTransitionDirection: string;
@ -85,6 +86,7 @@ export class BlockComponent implements OnInit, OnDestroy {
timeLtr: boolean;
childChangeSubscription: Subscription;
auditPrefSubscription: Subscription;
oobSubscription: Subscription;
priceSubscription: Subscription;
blockConversion: Price;
@ -168,6 +170,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.page = 1;
this.error = undefined;
this.fees = undefined;
this.oobFees = 0;
if (history.state.data && history.state.data.blockHeight) {
this.blockHeight = history.state.data.blockHeight;
@ -446,7 +449,7 @@ export class BlockComponent implements OnInit, OnDestroy {
inBlock[tx.txid] = true;
}
blockAudit.feeDelta = blockAudit.expectedFees > 0 ? (blockAudit.expectedFees - this.block.extras.totalFees) / blockAudit.expectedFees : 0;
blockAudit.feeDelta = blockAudit.expectedFees > 0 ? (blockAudit.expectedFees - (this.block.extras.totalFees + this.oobFees)) / blockAudit.expectedFees : 0;
blockAudit.weightDelta = blockAudit.expectedWeight > 0 ? (blockAudit.expectedWeight - this.block.weight) / blockAudit.expectedWeight : 0;
blockAudit.txDelta = blockAudit.template.length > 0 ? (blockAudit.template.length - this.block.tx_count) / blockAudit.template.length : 0;
this.blockAudit = blockAudit;
@ -462,6 +465,32 @@ export class BlockComponent implements OnInit, OnDestroy {
this.setupBlockGraphs();
});
this.oobSubscription = block$.pipe(
switchMap((block) => this.apiService.getAccelerationsByHeight$(block.height)
.pipe(
map(accelerations => {
return { block, accelerations };
}),
catchError((err) => {
return of({ block, accelerations: [] });
}))
),
).subscribe(({ block, accelerations}) => {
let totalFees = 0;
for (const acc of accelerations) {
totalFees += acc.boost_cost;
}
this.oobFees = totalFees;
if (block && this.block && this.blockAudit && block?.height === this.block?.height) {
this.blockAudit.feeDelta = this.blockAudit.expectedFees > 0 ? (this.blockAudit.expectedFees - (this.block.extras.totalFees + this.oobFees)) / this.blockAudit.expectedFees : 0;
}
},
(error) => {
this.error = error;
this.isLoadingBlock = false;
this.isLoadingOverview = false;
});
this.networkChangedSubscription = this.stateService.networkChanged$
.subscribe((network) => this.network = network);
@ -529,6 +558,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.unsubscribeNextBlockSubscriptions();
this.childChangeSubscription?.unsubscribe();
this.priceSubscription?.unsubscribe();
this.oobSubscription?.unsubscribe();
}
unsubscribeNextBlockSubscriptions() {

View File

@ -56,7 +56,7 @@
<app-time kind="since" [time]="block.timestamp" [fastRender]="true" [precision]="1" minUnit="minute"></app-time></div>
</ng-container>
</div>
<div class="animated" [class]="markHeight === block.height ? 'hide' : 'show'" *ngIf="block.extras?.pool != undefined">
<div class="animated" [class]="markHeight === block.height ? 'hide' : 'show'" *ngIf="block.extras?.pool != undefined && showPools">
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge badge-primary"
[routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
{{ block.extras.pool.name}}</a>

View File

@ -27,6 +27,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
@Input() minimal: boolean = false;
@Input() blockWidth: number = 125;
@Input() spotlight: number = 0;
@Input() showPools: boolean = true;
@Input() getHref?: (index, block) => string = (index, block) => `/block/${block.id}`;
specialBlocks = specialBlocks;

View File

@ -16,6 +16,7 @@
[minimal]="true"
[count]="blockchainBlocks"
[blockWidth]="blockWidth"
[showPools]="false"
[spotlight]="mode === 'mined' ? -index - 1 : 0"
[getHref]="getMinedUrl"
></app-blockchain-blocks>

View File

@ -16,9 +16,9 @@
</ng-container>
<a class="navbar-brand" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
<ng-template [ngIf]="subdomain">
<ng-template [ngIf]="subdomain && enterpriseInfo">
<div class="subdomain_container">
<img [src]="'/api/v1/services/enterprise/images/' + subdomain + '/logo'" class="subdomain_logo">
<img [src]="'/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo">
</div>
</ng-template>
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">

View File

@ -1,7 +1,7 @@
import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Env, StateService } from '../../services/state.service';
import { Observable, merge, of } from 'rxjs';
import { Observable, merge, of, Subscription } from 'rxjs';
import { LanguageService } from '../../services/language.service';
import { EnterpriseService } from '../../services/enterprise.service';
import { NavigationService } from '../../services/navigation.service';
@ -14,7 +14,7 @@ import { ApiService } from '../../services/api.service';
templateUrl: './master-page.component.html',
styleUrls: ['./master-page.component.scss'],
})
export class MasterPageComponent implements OnInit {
export class MasterPageComponent implements OnInit, OnDestroy {
@Input() headerVisible = true;
@Input() footerVisibleOverride: boolean | null = null;
@ -32,6 +32,9 @@ export class MasterPageComponent implements OnInit {
user: any = undefined;
servicesEnabled = false;
menuOpen = false;
enterpriseInfo: any;
enterpriseInfo$: Subscription;
@ViewChild(MenuComponent)
public menuComponent!: MenuComponent;
@ -64,6 +67,9 @@ export class MasterPageComponent implements OnInit {
this.footerVisible = this.footerVisibleOverride;
}
});
this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => {
this.enterpriseInfo = info;
});
this.servicesEnabled = this.officialMempoolSpace && this.stateService.env.ACCELERATOR === true && this.stateService.network === '';
this.refreshAuth();
@ -72,6 +78,12 @@ export class MasterPageComponent implements OnInit {
this.menuOpen = isServicesPage && !this.isSmallScreen();
}
ngOnDestroy() {
if (this.enterpriseInfo$) {
this.enterpriseInfo$.unsubscribe();
}
}
collapse(): void {
this.navCollapsed = !this.navCollapsed;
}

View File

@ -70,6 +70,26 @@
<app-tx-features [tx]="tx"></app-tx-features>
</td>
</tr>
<tr *ngIf="network === ''">
<td class="td-width" i18n="transaction.mining">Mining</td>
<td *ngIf="pool" class="wrap-cell">
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge mr-1"
[class]="pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
{{ pool.name }}
</a>
<ng-container *ngIf="auditStatus">
<span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
<ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template>
<ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template>
<ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template>
<span *ngIf="auditStatus.added" class="badge badge-primary mr-1" i18n-ngbTooltip="Added transaction tooltip" ngbTooltip="This transaction may have been added or prioritized out-of-band" placement="bottom" i18n="tx-features.tag.added|Added">Added</span>
<span *ngIf="auditStatus.conflict" class="badge badge-warning mr-1" i18n-ngbTooltip="Conflict in mempool tooltip" ngbTooltip="This transaction conflicted with another version in our mempool" placement="bottom" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
</ng-container>
</td>
<td *ngIf="!pool">
<span class="skeleton-loader"></span>
</td>
</tr>
</tbody>
</table>
</div>
@ -509,7 +529,7 @@
<ng-template #feeTable>
<table class="table table-borderless table-striped">
<tbody>
<tr *ngIf="isMobile && (network === 'liquid' || network === 'liquidtestnet' || !featuresEnabled)"></tr>
<tr *ngIf="isMobile && (network === 'liquid' || network === 'liquidtestnet' || !featuresEnabled || network === '')"></tr>
<tr>
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
<td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="tx.fee"></app-fiat></span></td>

View File

@ -149,6 +149,10 @@
.btn {
display: block;
}
&.wrap-cell {
white-space: normal;
}
}
}

View File

@ -8,10 +8,11 @@ import {
retryWhen,
delay,
mergeMap,
tap
tap,
map
} from 'rxjs/operators';
import { Transaction } from '../../interfaces/electrs.interface';
import { of, merge, Subscription, Observable, Subject, from, throwError } from 'rxjs';
import { of, merge, Subscription, Observable, Subject, from, throwError, combineLatest } from 'rxjs';
import { StateService } from '../../services/state.service';
import { CacheService } from '../../services/cache.service';
import { WebsocketService } from '../../services/websocket.service';
@ -28,6 +29,22 @@ import { isFeatureActive } from '../../bitcoin.utils';
import { ServicesApiServices } from '../../services/services-api.service';
import { EnterpriseService } from '../../services/enterprise.service';
interface Pool {
id: number;
name: string;
slug: string;
}
interface AuditStatus {
seen?: boolean;
expected?: boolean;
added?: boolean;
delayed?: number;
accelerated?: boolean;
conflict?: boolean;
coinbase?: boolean;
}
@Component({
selector: 'app-transaction',
templateUrl: './transaction.component.html',
@ -58,6 +75,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
urlFragmentSubscription: Subscription;
mempoolBlocksSubscription: Subscription;
blocksSubscription: Subscription;
miningSubscription: Subscription;
fragmentParams: URLSearchParams;
rbfTransaction: undefined | Transaction;
replaced: boolean = false;
@ -67,11 +85,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
accelerationInfo: Acceleration | null = null;
sigops: number | null;
adjustedVsize: number | null;
pool: Pool | null;
auditStatus: AuditStatus | null;
showCpfpDetails = false;
fetchCpfp$ = new Subject<string>();
fetchRbfHistory$ = new Subject<string>();
fetchCachedTx$ = new Subject<string>();
fetchAcceleration$ = new Subject<string>();
fetchMiningInfo$ = new Subject<{ hash: string, height: number, txid: string }>();
isCached: boolean = false;
now = Date.now();
da$: Observable<DifficultyAdjustment>;
@ -100,6 +121,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
acceleratorAvailable: boolean = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
showAccelerationSummary = false;
scrollIntoAccelPreview = false;
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
@ViewChild('graphContainer')
graphContainer: ElementRef;
@ -266,6 +288,54 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
});
this.miningSubscription = this.fetchMiningInfo$.pipe(
filter((target) => target.txid === this.txId),
tap(() => {
this.pool = null;
this.auditStatus = null;
}),
switchMap(({ hash, height, txid }) => {
const foundBlock = this.cacheService.getCachedBlock(height) || null;
const auditAvailable = this.isAuditAvailable(height);
const isCoinbase = this.tx.vin.some(v => v.is_coinbase);
const fetchAudit = auditAvailable && !isCoinbase;
return combineLatest([
foundBlock ? of(foundBlock.extras.pool) : this.apiService.getBlock$(hash).pipe(
map(block => {
return block.extras.pool;
}),
catchError(() => {
return of(null);
})
),
fetchAudit ? this.apiService.getBlockAudit$(hash).pipe(
map(audit => {
const isAdded = audit.addedTxs.includes(txid);
const isAccelerated = audit.acceleratedTxs.includes(txid);
const isConflict = audit.fullrbfTxs.includes(txid);
const isExpected = audit.template.some(tx => tx.txid === txid);
return {
seen: isExpected || !(isAdded || isConflict),
expected: isExpected,
added: isAdded,
conflict: isConflict,
accelerated: isAccelerated,
};
}),
catchError(() => {
return of(null);
})
) : of(isCoinbase ? { coinbase: true } : null)
]);
}),
catchError(() => {
return of(null);
})
).subscribe(([pool, auditStatus]) => {
this.pool = pool;
this.auditStatus = auditStatus;
});
this.mempoolPositionSubscription = this.stateService.mempoolTxPosition$.subscribe(txPosition => {
this.now = Date.now();
if (txPosition && txPosition.txid === this.txId && txPosition.position) {
@ -396,6 +466,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
}
} else {
this.fetchAcceleration$.next(tx.status.block_hash);
this.fetchMiningInfo$.next({ hash: tx.status.block_hash, height: tx.status.block_height, txid: tx.txid });
this.transactionTime = 0;
}
@ -453,6 +524,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.audioService.playSound('magic');
}
this.fetchAcceleration$.next(block.id);
this.fetchMiningInfo$.next({ hash: block.id, height: block.height, txid: this.tx.txid });
}
});
@ -606,6 +678,29 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.featuresEnabled = this.segwitEnabled || this.taprootEnabled || this.rbfEnabled;
}
isAuditAvailable(blockHeight: number): boolean {
if (!this.auditEnabled) {
return false;
}
switch (this.stateService.network) {
case 'testnet':
if (blockHeight < this.stateService.env.TESTNET_BLOCK_AUDIT_START_HEIGHT) {
return false;
}
break;
case 'signet':
if (blockHeight < this.stateService.env.SIGNET_BLOCK_AUDIT_START_HEIGHT) {
return false;
}
break;
default:
if (blockHeight < this.stateService.env.MAINNET_BLOCK_AUDIT_START_HEIGHT) {
return false;
}
}
return true;
}
resetTransaction() {
this.error = undefined;
this.tx = null;
@ -625,6 +720,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.accelerationInfo = null;
this.txInBlockIndex = null;
this.mempoolPosition = null;
this.pool = null;
this.auditStatus = null;
document.body.scrollTo(0, 0);
this.leaveTransaction();
}
@ -712,6 +809,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.mempoolPositionSubscription.unsubscribe();
this.mempoolBlocksSubscription.unsubscribe();
this.blocksSubscription.unsubscribe();
this.miningSubscription?.unsubscribe();
this.leaveTransaction();
}
}

View File

@ -236,7 +236,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
acc.unshift(stats);
acc = acc.slice(0, 120);
return acc;
}, mempoolStats)
}, (mempoolStats || []))
),
of(mempoolStats)
);

View File

@ -9878,16 +9878,179 @@ export const restApiDocsData = [
},
{
type: "category",
category: "accelerator",
fragment: "accelerator",
title: "Accelerator",
category: "accelerator-public",
fragment: "accelerator-public",
title: "Accelerator (Public)",
showConditions: [""],
options: { officialOnly: true },
},
{
options: { officialOnly: true },
type: "endpoint",
category: "accelerator",
category: "accelerator-public",
httpRequestMethod: "POST",
fragment: "accelerator-estimate",
title: "POST Calculate Estimated Costs",
description: {
default: "<p>Returns estimated costs to accelerate a transaction. Optionally set the <code>api_key</code> header to get customized estimation.</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-public",
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-public",
httpRequestMethod: "GET",
fragment: "accelerator-public-history",
title: "GET 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>4y</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>. <i>Note: This will return all acceleration requests accepted by the pool but the the listed transactions may have been mined by another pool.</i>
<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 <i>(min: 1)</i></a>
<li><code>pageLength</code>: the page lenght if using pagination <i>(min: 1, max: 50)</i></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"
}
]
}
]`,
},
}
}
},
{
type: "category",
category: "accelerator-private",
fragment: "accelerator-private",
title: "Accelerator (Authenticated)",
showConditions: [""],
options: { officialOnly: true },
},
{
options: { officialOnly: true },
type: "endpoint",
category: "accelerator-private",
httpRequestMethod: "GET",
fragment: "accelerator-deposit-history",
title: "GET Deposit History",
@ -9935,7 +10098,7 @@ export const restApiDocsData = [
{
options: { officialOnly: true },
type: "endpoint",
category: "accelerator",
category: "accelerator-private",
httpRequestMethod: "GET",
fragment: "accelerator-balance",
title: "GET Available Balance",
@ -9969,51 +10132,7 @@ export const restApiDocsData = [
{
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",
category: "accelerator-private",
httpRequestMethod: "POST",
fragment: "accelerator-accelerate",
title: "POST Accelerate A Transaction",
@ -10043,10 +10162,10 @@ export const restApiDocsData = [
{
options: { officialOnly: true },
type: "endpoint",
category: "accelerator",
category: "accelerator-private",
httpRequestMethod: "GET",
fragment: "accelerator-history",
title: "GET Private Acceleration History",
title: "GET 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>"
},
@ -10153,117 +10272,6 @@ export const restApiDocsData = [
}
]
}
]`,
},
}
}
},
{
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"
}
]
}
]`,
},
}

View File

@ -1,4 +1,4 @@
<div id="enterprise-cta-desktop">
<div id="enterprise-cta-desktop" *ngIf="officialMempoolInstance">
<p>Get higher API limits with Mempool Enterprise®</p>
<a class="btn btn-small btn-purple" href="/enterprise">More Info <fa-icon [icon]="['fas', 'angle-right']" [styles]="{'font-size': '12px'}"></fa-icon></a>
</div>

View File

@ -39,7 +39,7 @@
<div class="doc-content">
<div id="enterprise-cta-mobile" *ngIf="showMobileEnterpriseUpsell">
<div id="enterprise-cta-mobile" *ngIf="officialMempoolInstance && showMobileEnterpriseUpsell">
<p>Get higher API limits with <span class="no-line-break">Mempool Enterprise®</span></p>
<div class="button-group">
<a class="btn btn-small btn-secondary" (click)="showMobileEnterpriseUpsell = false">No Thanks</a>

View File

@ -403,4 +403,18 @@ export interface AccelerationHistoryParams {
blockHeight?: number;
page?: number;
pageLength?: number;
}
export interface AccelerationInfo {
txid: string,
height: number,
pool: {
id: number,
slug: string,
name: string,
},
effective_vsize: number,
effective_fee: number,
boost_rate: number,
boost_cost: number,
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume } from '../interfaces/node-api.interface';
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo } from '../interfaces/node-api.interface';
import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs';
import { StateService } from './state.service';
import { Transaction } from '../interfaces/electrs.interface';
@ -459,4 +459,22 @@ export class ApiService {
(queryParams.length > 0 ? `?${queryParams.join('&')}` : '')
);
}
getAccelerationsByPool$(slug: string): Observable<AccelerationInfo[]> {
return this.httpClient.get<AccelerationInfo[]>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/accelerations/pool/${slug}`
);
}
getAccelerationsByHeight$(height: number): Observable<AccelerationInfo[]> {
return this.httpClient.get<AccelerationInfo[]>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/accelerations/block/${height}`
);
}
getRecentAccelerations$(interval: string | undefined): Observable<AccelerationInfo[]> {
return this.httpClient.get<AccelerationInfo[]>(
this.apiBaseUrl + this.apiBasePath + '/api/v1/accelerations/interval' + (interval !== undefined ? `/${interval}` : '')
);
}
}

View File

@ -4,6 +4,7 @@ import { ApiService } from './api.service';
import { SeoService } from './seo.service';
import { StateService } from './state.service';
import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
@ -11,9 +12,9 @@ import { ActivatedRoute } from '@angular/router';
export class EnterpriseService {
exclusiveHostName = '.mempool.space';
subdomain: string | null = null;
info: object = {};
statsUrl: string;
siteId: number;
info$: BehaviorSubject<object> = new BehaviorSubject(null);
constructor(
@Inject(DOCUMENT) private document: Document,
@ -47,9 +48,9 @@ export class EnterpriseService {
fetchSubdomainInfo(): void {
this.apiService.getEnterpriseInfo$(this.subdomain).subscribe((info) => {
this.info = info;
this.insertMatomo(info.site_id);
this.seoService.setEnterpriseTitle(info.title);
this.info$.next(info);
},
(error) => {
if (error.status === 404) {

View File

@ -145,8 +145,8 @@ export class ServicesApiServices {
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`);
}
getAggregatedAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history/aggregated`, { params: { ...params } });
getAggregatedAccelerationHistory$(params: AccelerationHistoryParams): Observable<any> {
return this.httpClient.get<any>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history/aggregated`, { params: { ...params }, observe: 'response' });
}
getAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {

View File

@ -116,7 +116,7 @@ export class WebsocketService {
this.startMultiTrackTransaction(this.trackingTxId);
}
if (this.isTrackingMempoolBlock) {
this.startTrackMempoolBlock(this.trackingMempoolBlock);
this.startTrackMempoolBlock(this.trackingMempoolBlock, true);
}
if (this.isTrackingRbf) {
this.startTrackRbf(this.isTrackingRbf);
@ -197,9 +197,9 @@ export class WebsocketService {
this.websocketSubject.next({ 'track-asset': 'stop' });
}
startTrackMempoolBlock(block: number) {
startTrackMempoolBlock(block: number, force: boolean = false) {
// skip duplicate tracking requests
if (this.trackingMempoolBlock !== block) {
if (force || this.trackingMempoolBlock !== block) {
this.websocketSubject.next({ 'track-mempool-block': block });
this.isTrackingMempoolBlock = true;
this.trackingMempoolBlock = block;