Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4de2f2e951 | ||
|
|
61bbb95819 | ||
|
|
3fa32edf25 | ||
|
|
db220d9dfd | ||
|
|
bf61557879 | ||
|
|
ebaf5cd304 | ||
|
|
51154d3954 | ||
|
|
41b4b2eddf | ||
|
|
0dff7e82a3 | ||
|
|
9cba7ccf75 | ||
|
|
6177b97bd1 | ||
|
|
f7f1a99486 | ||
|
|
6b955acf9e | ||
|
|
530610add6 | ||
|
|
428f9369e2 | ||
|
|
a9defb21bb | ||
|
|
680e9562a0 | ||
|
|
66c5c303b3 | ||
|
|
5a86c8c83a | ||
|
|
725e9c0d95 | ||
|
|
74e59d6ea5 | ||
|
|
9ac45a6cc3 | ||
|
|
94d537daa6 | ||
|
|
30bc026c28 | ||
|
|
21942f8ab1 | ||
|
|
147f55fec3 | ||
|
|
e73f2cbdc1 | ||
|
|
009e18b622 | ||
|
|
5f20803e21 | ||
|
|
832d16cf2d | ||
|
|
5e8b5e75d8 | ||
|
|
c5ef1011d8 | ||
|
|
6a14043641 | ||
|
|
0f526f24cb | ||
|
|
4426bb10a9 | ||
|
|
18a7859cca | ||
|
|
349d491f7d | ||
|
|
7556424f0b | ||
|
|
1d827a9724 | ||
|
|
f019dd67b3 | ||
|
|
8b27ac1bbf | ||
|
|
b91774d50c | ||
|
|
22a5cd2de2 | ||
|
|
e5489277c6 | ||
|
|
04b6bee8a1 | ||
|
|
6cd8cf660b | ||
|
|
1b6fd29c82 | ||
|
|
a31dae67a8 | ||
|
|
76e3053207 | ||
|
|
985b7577e4 | ||
|
|
c748e5cda9 | ||
|
|
a99f45cd47 | ||
|
|
de1d7839b3 | ||
|
|
6aa3e38af2 | ||
|
|
dca7df709b | ||
|
|
e40e9f7d11 | ||
|
|
285bb357ba | ||
|
|
c3b9828d42 | ||
|
|
871e590305 | ||
|
|
0e5a1abb2b | ||
|
|
5665c6e6ec | ||
|
|
06b696f0bb | ||
|
|
75ca963bd5 | ||
|
|
3a6647eac0 | ||
|
|
9ad6b925c8 | ||
|
|
17720b98c1 | ||
|
|
99ea1ad0a0 | ||
|
|
64dd55b44b | ||
|
|
e2d2a8da26 | ||
|
|
2325d29f8e | ||
|
|
0e37e85af6 | ||
|
|
4b3123b5ae |
No files matched your search
@@ -42,6 +42,7 @@ class BitcoinRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', this.getBlock)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/summary', this.getStrippedBlockTransactions)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/audit-summary', this.getBlockAuditSummary)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/tx/:txid/audit', this.$getBlockTxAuditSummary)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/height', this.getBlockTipHeight)
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
||||
@@ -361,6 +362,20 @@ class BitcoinRoutes {
|
||||
}
|
||||
}
|
||||
|
||||
private async $getBlockTxAuditSummary(req: Request, res: Response) {
|
||||
try {
|
||||
const auditSummary = await blocks.$getBlockTxAuditSummary(req.params.hash, req.params.txid);
|
||||
if (auditSummary) {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
|
||||
res.json(auditSummary);
|
||||
} else {
|
||||
return res.status(404).send(`transaction audit not available`);
|
||||
}
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getBlocks(req: Request, res: Response) {
|
||||
try {
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
|
||||
|
||||
@@ -2,7 +2,7 @@ import config from '../config';
|
||||
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
||||
import logger from '../logger';
|
||||
import memPool from './mempool';
|
||||
import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionMinerInfo, CpfpSummary, MempoolTransactionExtended, TransactionClassified, BlockAudit } from '../mempool.interfaces';
|
||||
import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionMinerInfo, CpfpSummary, MempoolTransactionExtended, TransactionClassified, BlockAudit, TransactionAudit } from '../mempool.interfaces';
|
||||
import { Common } from './common';
|
||||
import diskCache from './disk-cache';
|
||||
import transactionUtils from './transaction-utils';
|
||||
@@ -1359,6 +1359,14 @@ class Blocks {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getBlockTxAuditSummary(hash: string, txid: string): Promise<TransactionAudit | null> {
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
|
||||
return BlocksAuditsRepository.$getBlockTxAudit(hash, txid);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public getLastDifficultyAdjustmentTime(): number {
|
||||
return this.lastDifficultyAdjustmentTime;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,19 @@ export interface BlockAudit {
|
||||
matchRate: number,
|
||||
expectedFees?: number,
|
||||
expectedWeight?: number,
|
||||
template?: any[];
|
||||
}
|
||||
|
||||
export interface TransactionAudit {
|
||||
seen?: boolean;
|
||||
expected?: boolean;
|
||||
added?: boolean;
|
||||
prioritized?: boolean;
|
||||
delayed?: number;
|
||||
accelerated?: boolean;
|
||||
conflict?: boolean;
|
||||
coinbase?: boolean;
|
||||
firstSeen?: number;
|
||||
}
|
||||
|
||||
export interface AuditScore {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import blocks from '../api/blocks';
|
||||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import { BlockAudit, AuditScore } from '../mempool.interfaces';
|
||||
import { BlockAudit, AuditScore, TransactionAudit } from '../mempool.interfaces';
|
||||
|
||||
class BlocksAuditRepositories {
|
||||
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
||||
@@ -98,6 +98,41 @@ class BlocksAuditRepositories {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getBlockTxAudit(hash: string, txid: string): Promise<TransactionAudit | null> {
|
||||
try {
|
||||
const blockAudit = await this.$getBlockAudit(hash);
|
||||
|
||||
if (blockAudit) {
|
||||
const isAdded = blockAudit.addedTxs.includes(txid);
|
||||
const isPrioritized = blockAudit.prioritizedTxs.includes(txid);
|
||||
const isAccelerated = blockAudit.acceleratedTxs.includes(txid);
|
||||
const isConflict = blockAudit.fullrbfTxs.includes(txid);
|
||||
let isExpected = false;
|
||||
let firstSeen = undefined;
|
||||
blockAudit.template?.forEach(tx => {
|
||||
if (tx.txid === txid) {
|
||||
isExpected = true;
|
||||
firstSeen = tx.time;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
seen: isExpected || isPrioritized || isAccelerated,
|
||||
expected: isExpected,
|
||||
added: isAdded,
|
||||
prioritized: isPrioritized,
|
||||
conflict: isConflict,
|
||||
accelerated: isAccelerated,
|
||||
firstSeen,
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot fetch block transaction audit from db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getBlockAuditScore(hash: string): Promise<AuditScore> {
|
||||
try {
|
||||
const [rows]: any[] = await DB.query(
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of May 21, 2024.
|
||||
|
||||
Signed: hans-crypto
|
||||
@@ -0,0 +1,3 @@
|
||||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of July 12, 2024.
|
||||
|
||||
Signed: jlopp
|
||||
@@ -149,7 +149,7 @@ __MEMPOOL_SERVICES_ACCELERATIONS__=${MEMPOOL_SERVICES_ACCELERATIONS:=false}
|
||||
|
||||
# REDIS
|
||||
__REDIS_ENABLED__=${REDIS_ENABLED:=false}
|
||||
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=true}
|
||||
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=""}
|
||||
__REDIS_BATCH_QUERY_BASE_SIZE__=${REDIS_BATCH_QUERY_BASE_SIZE:=5000}
|
||||
|
||||
# FIAT_PRICE
|
||||
|
||||
Generated
+25
-28
@@ -23,9 +23,9 @@
|
||||
"@angular/router": "^17.3.1",
|
||||
"@angular/ssr": "^17.3.1",
|
||||
"@fortawesome/angular-fontawesome": "~0.14.1",
|
||||
"@fortawesome/fontawesome-common-types": "~6.5.1",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.5.1",
|
||||
"@fortawesome/fontawesome-common-types": "~6.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.6.0",
|
||||
"@mempool/mempool.js": "2.3.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@types/qrcode": "~1.5.0",
|
||||
@@ -3669,33 +3669,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz",
|
||||
"integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==",
|
||||
"hasInstallScript": true,
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
|
||||
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz",
|
||||
"integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==",
|
||||
"hasInstallScript": true,
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
|
||||
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz",
|
||||
"integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==",
|
||||
"hasInstallScript": true,
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
|
||||
"integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -20797,24 +20794,24 @@
|
||||
}
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz",
|
||||
"integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A=="
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
|
||||
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="
|
||||
},
|
||||
"@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz",
|
||||
"integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
|
||||
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz",
|
||||
"integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
|
||||
"integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
}
|
||||
},
|
||||
"@goto-bus-stop/common-shake": {
|
||||
|
||||
@@ -76,9 +76,9 @@
|
||||
"@angular/router": "^17.3.1",
|
||||
"@angular/ssr": "^17.3.1",
|
||||
"@fortawesome/angular-fontawesome": "~0.14.1",
|
||||
"@fortawesome/fontawesome-common-types": "~6.5.1",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.5.1",
|
||||
"@fortawesome/fontawesome-common-types": "~6.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.6.0",
|
||||
"@mempool/mempool.js": "2.3.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@types/qrcode": "~1.5.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@if (accelerateError) {
|
||||
<div class="row mb-1 text-center">
|
||||
<div class="col-sm">
|
||||
<h1 style="font-size: larger;">Sorry, something went wrong!</h1>
|
||||
<h1 style="font-size: larger;" i18n="accelerator.sorry-error-title">Sorry, something went wrong!</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center mt-1">
|
||||
@@ -552,22 +552,15 @@
|
||||
<ng-template #accelerateTo let-x i18n="accelerator.accelerate-to-x">Accelerate to ~{{ x | number : '1.0-0' }} sat/vB</ng-template>
|
||||
|
||||
<ng-template #accelerateButton>
|
||||
@if (!couldPay && !quoteError && !(estimate?.availablePaymentMethods.bitcoin || estimate?.availablePaymentMethods.balance)) {
|
||||
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center disabled" style="width: 200px">
|
||||
<div class="position-relative">
|
||||
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.disabled]="!canPay || quoteError || cantPayReason || calculating || (!advancedEnabled && selectedOption !== 'accel')" style="width: 200px" (click)="moveToStep('checkout')">
|
||||
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
|
||||
<span>Coming soon</span>
|
||||
<span i18n="transaction.accelerate|Accelerate button label">Accelerate</span>
|
||||
</button>
|
||||
} @else {
|
||||
<div class="position-relative">
|
||||
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" [class.disabled]="!canPay || quoteError || cantPayReason || calculating || (!advancedEnabled && selectedOption !== 'accel')" style="width: 200px" (click)="moveToStep('checkout')">
|
||||
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
|
||||
<span i18n="transaction.accelerate|Accelerate button label">Accelerate</span>
|
||||
</button>
|
||||
@if (quoteError || cantPayReason) {
|
||||
<div class="btn-error-wrapper"><span class="btn-error"><app-mempool-error [error]="quoteError || cantPayReason" [textOnly]="true" alertClass=""></app-mempool-error></span></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (quoteError || cantPayReason) {
|
||||
<div class="btn-error-wrapper"><span class="btn-error"><app-mempool-error [error]="quoteError || cantPayReason" [textOnly]="true" alertClass=""></app-mempool-error></span></div>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #accountPayButton>
|
||||
|
||||
@@ -297,6 +297,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
|
||||
this.validateChoice();
|
||||
|
||||
if (!this.couldPay) {
|
||||
this.quoteError = `cannot_accelerate_tx`;
|
||||
if (this.step === 'summary') {
|
||||
this.unavailable.emit(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.step === 'checkout' && this.canPayWithBitcoin && !this.loadingBtcpayInvoice) {
|
||||
this.loadingBtcpayInvoice = true;
|
||||
this.requestBTCPayInvoice();
|
||||
|
||||
@@ -88,7 +88,7 @@ export class AccelerateFeeGraphComponent implements OnInit, AfterViewInit, OnCha
|
||||
rate: option.rate,
|
||||
height: lastHeight,
|
||||
class: 'max',
|
||||
label: this.showEstimate ? $localize`maximum` : $localize`:@@25fbf6e80a945703c906a5a7d8c92e8729c7ab21:accelerated`,
|
||||
label: this.showEstimate ? $localize`maximum` : $localize`accelerated`,
|
||||
active: option.index === this.maxRateIndex,
|
||||
rateIndex: option.index,
|
||||
fee: option.fee,
|
||||
|
||||
+86
-105
@@ -1,6 +1,38 @@
|
||||
@if (tx.status.confirmed) {
|
||||
<div class="acceleration-timeline box">
|
||||
<div class="acceleration-timeline box" [class.lower-padding]="!tx.status.confirmed">
|
||||
<div class="timeline-wrapper">
|
||||
@if (!tx.status.confirmed) {
|
||||
<div class="timeline">
|
||||
<div class="intervals">
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval-spacer"></div>
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval">
|
||||
<div class="interval-time">
|
||||
@if (eta) {
|
||||
~<app-time [time]="eta?.wait / 1000"></app-time> <!-- <span *ngIf="accelerateRatio > 1" class="compare"> ({{ accelerateRatio }}x faster)</span> -->
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-spacer"></div>
|
||||
</div>
|
||||
<div class="nodes">
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval-spacer"></div>
|
||||
<div class="node">
|
||||
<div class="acc-to-confirmed right go-faster"></div>
|
||||
</div>
|
||||
<div class="interval-spacer">
|
||||
</div>
|
||||
<div class="node" [id]="'confirmed'">
|
||||
<div class="acc-to-confirmed left go-faster"></div>
|
||||
<div class="shape-border waiting">
|
||||
<div class="shape animate"></div>
|
||||
</div>
|
||||
<div class="status"><span class="badge badge-waiting" i18n="transaction.rbf.mined">Mined</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="timeline">
|
||||
<div class="intervals">
|
||||
<div class="node-spacer"></div>
|
||||
@@ -12,7 +44,13 @@
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval">
|
||||
<div class="interval-time">
|
||||
<app-time [time]="tx.status.block_time - acceleratedAt"></app-time>
|
||||
@if (tx.status.confirmed) {
|
||||
<div class="interval-time">
|
||||
<app-time [time]="tx.status.block_time - acceleratedAt"></app-time>
|
||||
</div>
|
||||
} @else if (standardETA && !tx.status.confirmed) {
|
||||
<!-- ~<app-time [time]="standardETA / 1000 - now"></app-time> -->
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-spacer"></div>
|
||||
@@ -25,128 +63,71 @@
|
||||
</div>
|
||||
<div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div>
|
||||
<div class="time">
|
||||
<app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time>
|
||||
@if (useAbsoluteTime) {
|
||||
<span>{{ transactionTime * 1000 | date }}</span>
|
||||
} @else {
|
||||
<app-time kind="since" [time]="transactionTime"></app-time>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="interval-spacer">
|
||||
<div class="seen-to-acc"></div>
|
||||
</div>
|
||||
<div class="node" [id]="'accelerated'">
|
||||
<div class="node" [class.accelerated]="!tx.status.confirmed" [id]="'accelerated'">
|
||||
<div class="seen-to-acc left"></div>
|
||||
<div class="acc-to-confirmed right"></div>
|
||||
<div class="shape-border">
|
||||
@if (tx.status.confirmed) {
|
||||
<div class="acc-to-confirmed right"></div>
|
||||
} @else {
|
||||
<div class="seen-to-acc right"></div>
|
||||
}
|
||||
<div class="shape-border">
|
||||
<div class="shape"></div>
|
||||
@if (!tx.status.confirmed) {
|
||||
<div class="connector down loading"></div>
|
||||
}
|
||||
</div>
|
||||
<div class="status"><span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span></div>
|
||||
<div class="time">
|
||||
<app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
||||
@if (tx.status.confirmed) {
|
||||
<div class="status"><span class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span></div>
|
||||
}
|
||||
<div class="time offset-left" [class.no-margin]="!tx.status.confirmed">
|
||||
@if (!tx.status.confirmed) {
|
||||
<span i18n="transaction.audit.accelerated">Accelerated</span>{{ "" }}
|
||||
}
|
||||
@if (useAbsoluteTime) {
|
||||
<span>{{ acceleratedAt * 1000 | date }}</span>
|
||||
} @else {
|
||||
<app-time kind="since" [time]="acceleratedAt" [lowercaseStart]="true"></app-time>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="interval-spacer">
|
||||
@if (tx.status.confirmed) {
|
||||
<div class="acc-to-confirmed"></div>
|
||||
} @else {
|
||||
<div class="seen-to-acc"></div>
|
||||
}
|
||||
</div>
|
||||
<div class="node selected" [id]="'confirmed'">
|
||||
<div class="acc-to-confirmed left" ></div>
|
||||
<div class="shape-border">
|
||||
<div class="node" [class.selected]="tx.status.confirmed" [id]="'confirmed'">
|
||||
@if (tx.status.confirmed) {
|
||||
<div class="acc-to-confirmed left"></div>
|
||||
} @else {
|
||||
<div class="seen-to-acc left"></div>
|
||||
}
|
||||
<div class="shape-border" [class.waiting]="!tx.status.confirmed">
|
||||
<div class="shape"></div>
|
||||
</div>
|
||||
@if (tx.status.confirmed) {
|
||||
<div class="status"><span class="badge badge-success" i18n="transaction.rbf.mined">Mined</span></div>
|
||||
<div class="time">
|
||||
@if (useAbsoluteTime) {
|
||||
<span>{{ tx.status.block_time * 1000 | date }}</span>
|
||||
} @else {
|
||||
<app-time kind="since" [time]="tx.status.block_time"></app-time>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @else if (acceleratedETA) { <!-- Not yet accelerated; to be shown only in acceleration checkout -->
|
||||
} @else if (standardETA) { <!-- Accelerated -->
|
||||
<div class="acceleration-timeline box lower-padding">
|
||||
<div class="timeline-wrapper">
|
||||
<div class="timeline">
|
||||
<div class="intervals">
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval-spacer"></div>
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval">
|
||||
<div class="interval-time">
|
||||
@if (eta) {
|
||||
~<app-time [time]="eta?.wait / 1000"></app-time> <!-- <span *ngIf="accelerateRatio > 1" class="compare"> ({{ accelerateRatio }}x faster)</span> -->
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-spacer"></div>
|
||||
</div>
|
||||
<div class="nodes">
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval-spacer"></div>
|
||||
<div class="node">
|
||||
<div class="acc-to-confirmed right go-faster"></div>
|
||||
</div>
|
||||
<div class="interval-spacer">
|
||||
</div>
|
||||
<div class="node" [id]="'confirmed'">
|
||||
<div class="acc-to-confirmed left go-faster"></div>
|
||||
<div class="shape-border waiting">
|
||||
<div class="shape animate"></div>
|
||||
</div>
|
||||
<div class="status"><span class="badge badge-waiting" i18n="transaction.rbf.mined">Mined</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline">
|
||||
<div class="intervals">
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval">
|
||||
<div class="interval-time">
|
||||
<app-time [time]="acceleratedAt - transactionTime"></app-time>
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-spacer"></div>
|
||||
<div class="interval">
|
||||
<div class="interval-time">
|
||||
<!-- ~<app-time [time]="standardETA / 1000 - now"></app-time> -->
|
||||
-
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-spacer"></div>
|
||||
</div>
|
||||
<div class="nodes">
|
||||
<div class="node" [id]="'first-seen'">
|
||||
<div class="seen-to-acc right"></div>
|
||||
<div class="shape-border">
|
||||
<div class="shape"></div>
|
||||
</div>
|
||||
<div class="status"><span class="badge badge-primary" i18n="transaction.first-seen|Transaction first seen">First seen</span></div>
|
||||
<div class="time">
|
||||
<app-time *ngIf="transactionTime > 0" kind="since" [time]="transactionTime"></app-time>
|
||||
</div>
|
||||
</div>
|
||||
<div class="interval-spacer">
|
||||
<div class="seen-to-acc"></div>
|
||||
</div>
|
||||
<div class="node accelerated" [id]="'accelerated'">
|
||||
<div class="seen-to-acc left"></div>
|
||||
<div class="seen-to-acc right"></div>
|
||||
<div class="shape-border">
|
||||
<div class="shape"></div>
|
||||
<div class="connector down loading"></div>
|
||||
</div>
|
||||
<div class="time" style="margin-top: 3px;">
|
||||
<span i18n="transaction.audit.accelerated">Accelerated</span> <app-time *ngIf="acceleratedAt" kind="since" [time]="acceleratedAt"></app-time>
|
||||
</div>
|
||||
</div>
|
||||
<div class="interval-spacer">
|
||||
<div class="seen-to-acc"></div>
|
||||
</div>
|
||||
<div class="node" [id]="'confirmed'">
|
||||
<div class="seen-to-acc left"></div>
|
||||
<div class="shape-border waiting">
|
||||
<div class="shape"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
+12
-2
@@ -209,7 +209,7 @@
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: -64px;
|
||||
margin-top: -66px;
|
||||
|
||||
.badge.badge-waiting {
|
||||
opacity: 0.5;
|
||||
@@ -224,10 +224,20 @@
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-top: 33px;
|
||||
margin-top: 32px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
white-space: nowrap;
|
||||
|
||||
&.offset-left {
|
||||
@media (max-width: 650px) {
|
||||
margin-left: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-margin {
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -19,15 +19,23 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||
acceleratedAt: number;
|
||||
now: number;
|
||||
accelerateRatio: number;
|
||||
useAbsoluteTime: boolean = false;
|
||||
interval: number;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000;
|
||||
this.now = Math.floor(new Date().getTime() / 1000);
|
||||
this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
|
||||
|
||||
this.interval = window.setInterval(() => {
|
||||
this.now = Math.floor(new Date().getTime() / 1000);
|
||||
this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
ngOnChanges(changes): void {
|
||||
this.now = Math.floor(new Date().getTime() / 1000);
|
||||
// Hide standard ETA while we don't have a proper standard ETA calculation, see https://github.com/mempool/mempool/issues/65
|
||||
|
||||
// if (changes?.eta?.currentValue || changes?.standardETA?.currentValue || changes?.acceleratedETA?.currentValue) {
|
||||
@@ -40,4 +48,8 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -45,8 +45,8 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||
(chartInit)="onChartInit($event)" [style]="{opacity: isLoading ? 0.5 : 1}">
|
||||
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null, opacity: isLoading ? 0.5 : 1 }" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||
(chartInit)="onChartInit($event)">
|
||||
</div>
|
||||
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||
<div class="spinner-border text-light"></div>
|
||||
|
||||
+21
-5
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, NgZone, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { EChartsOption } from '../../../graphs/echarts';
|
||||
import { Observable, Subject, Subscription, combineLatest, fromEvent, merge, share } from 'rxjs';
|
||||
import { startWith, switchMap, tap } from 'rxjs/operators';
|
||||
@@ -8,10 +8,11 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../../shared/graphs.utils';
|
||||
import { StorageService } from '../../../services/storage.service';
|
||||
import { MiningService } from '../../../services/mining.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { RelativeUrlPipe } from '../../../shared/pipes/relative-url/relative-url.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-acceleration-fees-graph',
|
||||
@@ -32,7 +33,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
@Input() height: number = 300;
|
||||
@Input() right: number | string = 45;
|
||||
@Input() left: number | string = 75;
|
||||
@Input() period: '3d' | '1w' | '1m' = '1w';
|
||||
@Input() period: '24h' | '3d' | '1w' | '1m' | 'all' = '1w';
|
||||
@Input() accelerations$: Observable<Acceleration[]>;
|
||||
|
||||
miningWindowPreference: string;
|
||||
@@ -48,7 +49,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
isLoading = true;
|
||||
formatNumber = formatNumber;
|
||||
timespan = '';
|
||||
periodSubject$: Subject<'3d' | '1w' | '1m'> = new Subject();
|
||||
periodSubject$: Subject<'24h' | '3d' | '1w' | '1m' | 'all'> = new Subject();
|
||||
chartInstance: any = undefined;
|
||||
daysAvailable: number = 0;
|
||||
|
||||
@@ -62,6 +63,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
private route: ActivatedRoute,
|
||||
public stateService: StateService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private router: Router,
|
||||
private zone: NgZone,
|
||||
) {
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1w' });
|
||||
this.radioGroupForm.controls.dateSpan.setValue('1w');
|
||||
@@ -78,7 +81,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
|
||||
this.route.fragment.subscribe((fragment) => {
|
||||
if (['24h', '3d', '1w', '1m', '3m'].indexOf(fragment) > -1) {
|
||||
if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) {
|
||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||
}
|
||||
});
|
||||
@@ -294,6 +297,19 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
|
||||
onChartInit(ec) {
|
||||
this.chartInstance = ec;
|
||||
|
||||
this.chartInstance.on('click', (e) => {
|
||||
this.zone.run(() => {
|
||||
if (['24h', '3d'].includes(this.timespan)) {
|
||||
const url = new RelativeUrlPipe(this.stateService).transform(`/block/${e.data[2]}`);
|
||||
if (e.event.event.shiftKey || e.event.event.ctrlKey || e.event.event.metaKey) {
|
||||
window.open(url);
|
||||
} else {
|
||||
this.router.navigate([url]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isMobile() {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<h5 class="card-title" i18n="accelerator.requests">Requests</h5>
|
||||
<div class="card-text">
|
||||
<div>{{ stats.totalRequested }}</div>
|
||||
<div class="symbol" i18n="accelerator.total-accelerated">accelerated</div>
|
||||
<div class="symbol" i18n="accelerator.total-accelerated-plural">accelerated</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
|
||||
+7
-1
@@ -16,7 +16,7 @@ export type AccelerationStats = {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccelerationStatsComponent implements OnInit, OnChanges {
|
||||
@Input() timespan: '3d' | '1w' | '1m' = '1w';
|
||||
@Input() timespan: '24h' | '3d' | '1w' | '1m' | 'all' = '1w';
|
||||
accelerationStats$: Observable<AccelerationStats>;
|
||||
blocksInPeriod: number = 7 * 144;
|
||||
|
||||
@@ -35,6 +35,9 @@ export class AccelerationStatsComponent implements OnInit, OnChanges {
|
||||
updateStats(): void {
|
||||
this.accelerationStats$ = this.servicesApiService.getAccelerationStats$({ timeframe: this.timespan });
|
||||
switch (this.timespan) {
|
||||
case '24h':
|
||||
this.blocksInPeriod = 144;
|
||||
break;
|
||||
case '3d':
|
||||
this.blocksInPeriod = 3 * 144;
|
||||
break;
|
||||
@@ -44,6 +47,9 @@ export class AccelerationStatsComponent implements OnInit, OnChanges {
|
||||
case '1m':
|
||||
this.blocksInPeriod = 30 * 144;
|
||||
break;
|
||||
case 'all':
|
||||
this.blocksInPeriod = Infinity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -23,12 +23,18 @@
|
||||
<div class="main-title">
|
||||
<span [attr.data-cy]="'acceleration-stats'" i18n="accelerator.acceleration-stats">Acceleration stats</span>
|
||||
@switch (timespan) {
|
||||
@case ('24h') {
|
||||
<span style="font-size: xx-small" i18n="mining.1-day">(1 day)</span>
|
||||
}
|
||||
@case ('1w') {
|
||||
<span style="font-size: xx-small" i18n="mining.1-week">(1 week)</span>
|
||||
}
|
||||
@case ('1m') {
|
||||
<span style="font-size: xx-small" i18n="mining.1-month">(1 month)</span>
|
||||
}
|
||||
@case ('all') {
|
||||
<span style="font-size: xx-small" i18n="mining.all-time">(all time)</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="card-wrapper">
|
||||
@@ -36,11 +42,17 @@
|
||||
<div class="card-body more-padding">
|
||||
<app-acceleration-stats [timespan]="timespan"></app-acceleration-stats>
|
||||
<div class="widget-toggler">
|
||||
<a href="" (click)="setTimespan('24h')" class="toggler-option"
|
||||
[ngClass]="{'inactive': timespan === '24h'}"><small>24h</small></a>
|
||||
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
||||
<a href="" (click)="setTimespan('1w')" class="toggler-option"
|
||||
[ngClass]="{'inactive': timespan === '1w'}"><small>1w</small></a>
|
||||
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
||||
<a href="" (click)="setTimespan('1m')" class="toggler-option"
|
||||
[ngClass]="{'inactive': timespan === '1m'}"><small>1m</small></a>
|
||||
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
||||
<a href="" (click)="setTimespan('all')" class="toggler-option"
|
||||
[ngClass]="{'inactive': timespan === 'all'}"><small>all</small></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@ export class AcceleratorDashboardComponent implements OnInit, OnDestroy {
|
||||
webGlEnabled = true;
|
||||
seen: Set<string> = new Set();
|
||||
firstLoad = true;
|
||||
timespan: '3d' | '1w' | '1m' = '1w';
|
||||
timespan: '24h' | '3d' | '1w' | '1m' | 'all' = '1w';
|
||||
|
||||
accelerationDeltaSubscription: Subscription;
|
||||
|
||||
@@ -99,7 +99,7 @@ export class AcceleratorDashboardComponent implements OnInit, OnDestroy {
|
||||
this.minedAccelerations$ = this.stateService.chainTip$.pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap(() => {
|
||||
return this.serviceApiServices.getAccelerationHistory$({ status: 'completed', pageLength: 6 }).pipe(
|
||||
return this.serviceApiServices.getAccelerationHistory$({ status: 'completed_provisional,completed', pageLength: 6 }).pipe(
|
||||
catchError(() => {
|
||||
return of([]);
|
||||
}),
|
||||
|
||||
+15
-1
@@ -18,7 +18,12 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="pie-chart" rowspan="2" *ngIf="!chartPositionLeft">
|
||||
<ng-container *ngTemplateOutlet="pieChart"></ng-container>
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
@if (hasCpfp) {
|
||||
<button type="button" class="btn btn-outline-info btn-sm btn-small-height float-right mt-0" (click)="onToggleCpfp()">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>
|
||||
}
|
||||
<ng-container *ngTemplateOutlet="pieChart"></ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -27,6 +32,15 @@
|
||||
<ng-container i18n="accelerator.x-of-hash-rate">{{ acceleratedByPercentage }} <span class="symbol hashrate-label">of hashrate</span></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
@if (hasCpfp && chartPositionLeft) {
|
||||
<tr>
|
||||
<td colspan="3" class="pt-0">
|
||||
<div class="d-flex justify-content-end align-items-start">
|
||||
<button type="button" class="btn btn-outline-info btn-sm btn-small-height float-right mt-0" (click)="onToggleCpfp()">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, OnChanges, SimpleChanges, EventEmitter } from '@angular/core';
|
||||
import { Transaction } from '../../../interfaces/electrs.interface';
|
||||
import { Acceleration, SinglePoolStats } from '../../../interfaces/node-api.interface';
|
||||
import { EChartsOption, PieSeriesOption } from '../../../graphs/echarts';
|
||||
@@ -27,8 +27,10 @@ export class ActiveAccelerationBox implements OnChanges {
|
||||
@Input() accelerationInfo: Acceleration;
|
||||
@Input() miningStats: MiningStats;
|
||||
@Input() pools: number[];
|
||||
@Input() hasCpfp: boolean = false;
|
||||
@Input() chartOnly: boolean = false;
|
||||
@Input() chartPositionLeft: boolean = false;
|
||||
@Output() toggleCpfp = new EventEmitter();
|
||||
|
||||
acceleratedByPercentage: string = '';
|
||||
|
||||
@@ -133,4 +135,8 @@ export class ActiveAccelerationBox implements OnChanges {
|
||||
}
|
||||
this.chartInstance = ec;
|
||||
}
|
||||
|
||||
onToggleCpfp(): void {
|
||||
this.toggleCpfp.emit();
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,10 @@ export class BitcoinInvoiceComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
this.paymentStatusSubscription = this.apiService.getPaymentStatus$(this.invoice.btcpayInvoiceId).pipe(
|
||||
retry({ delay: () => timer(2000)})
|
||||
).subscribe((result) => {
|
||||
).subscribe((response) => {
|
||||
if (response.status === 204 || response.status === 404) {
|
||||
return;
|
||||
}
|
||||
this.paymentStatus = 3;
|
||||
this.completed.emit();
|
||||
});
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block?.extras.pool.name }}
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
|
||||
@@ -65,3 +65,11 @@
|
||||
.badge {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.pool-logo {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template #loading>
|
||||
<div class="text-center mb-4" class="tx-skeleton">
|
||||
<div class="tx-skeleton">
|
||||
<ng-container *ngIf="(txsLoadingStatus$ | async) as txsLoadingStatus; else headerLoader">
|
||||
<div class="header-bg box">
|
||||
<div class="progress progress-dark" style="margin: 4px; height: 14px;">
|
||||
|
||||
@@ -338,7 +338,7 @@
|
||||
<ngb-pagination class="pagination-container float-right" [disabled]="true" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="stateService.env.ITEMS_PER_PAGE" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="text-center mb-4" class="tx-skeleton">
|
||||
<div class="tx-skeleton">
|
||||
|
||||
<div class="header-bg box">
|
||||
<span class="skeleton-loader"></span>
|
||||
|
||||
@@ -68,11 +68,17 @@
|
||||
<ng-template #mempoolTable let-mempoolInfoData>
|
||||
<div class="mempool-info-data">
|
||||
<div class="item">
|
||||
<h5 *ngIf="!mempoolInfoData.value || mempoolInfoData.value.memPoolInfo.mempoolminfee === mempoolInfoData.value.memPoolInfo.minrelaytxfee || (stateService.env.BASE_MODULE === 'liquid' && mempoolInfoData.value.memPoolInfo.mempoolminfee === 0.000001) else purgingText" class="card-title" i18n="dashboard.minimum-fee|Minimum mempool fee">Minimum fee</h5>
|
||||
<ng-template #purgingText><h5 class="card-title" i18n="dashboard.purging|Purgin below fee">Purging</h5></ng-template>
|
||||
<p class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loading">
|
||||
<ng-template [ngIf]="mempoolInfoData.value.memPoolInfo.mempoolminfee !== mempoolInfoData.value.memPoolInfo.minrelaytxfee">< </ng-template><app-fee-rate [fee]="mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000"></app-fee-rate>
|
||||
</p>
|
||||
@if (!mempoolInfoData.value || mempoolInfoData.value.memPoolInfo.mempoolminfee === mempoolInfoData.value.memPoolInfo.minrelaytxfee || (stateService.env.BASE_MODULE === 'liquid' && mempoolInfoData.value.memPoolInfo.mempoolminfee === 0.000001)) {
|
||||
<h5 class="card-title" i18n="dashboard.minimum-fee|Minimum mempool fee">Minimum fee</h5>
|
||||
<p class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loading">
|
||||
<app-fee-rate [fee]="mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000"></app-fee-rate>
|
||||
</p>
|
||||
} @else {
|
||||
<h5 class="card-title" i18n="dashboard.purging|Purging below fee">Purging</h5>
|
||||
<p class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loading" i18n-ngbTooltip="dashboard.purging-desc" ngbTooltip="Fee rate below which transactions are purged from default Bitcoin Core nodes" placement="bottom">
|
||||
< <app-fee-rate [fee]="mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000"></app-fee-rate>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.unconfirmed|Unconfirmed count">Unconfirmed</h5>
|
||||
@@ -83,7 +89,7 @@
|
||||
<div class="item bar">
|
||||
<h5 class="card-title" i18n="dashboard.memory-usage|Memory usage">Memory Usage</h5>
|
||||
<div class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loadingbig">
|
||||
<div class="progress">
|
||||
<div class="progress" i18n-ngbTooltip="dashboard.memory-usage-desc" ngbTooltip="Memory used by our mempool (may exceed default Bitcoin Core limit)" placement="bottom">
|
||||
<div class="progress-bar {{ mempoolInfoData.value.mempoolSizeProgress }}" role="progressbar" [ngStyle]="{'width': (mempoolInfoData.value.memPoolInfo.usage / mempoolInfoData.value.memPoolInfo.maxmempool * 100) + '%' }"> </div>
|
||||
<div class="progress-text">‎<span [innerHTML]="mempoolInfoData.value.memPoolInfo.usage | bytes : 2 : 'B' : null : false : 3"></span> / <span [innerHTML]="mempoolInfoData.value.memPoolInfo.maxmempool | bytes"></span></div>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,12 @@
|
||||
<li class="nav-item d-flex justify-content-start align-items-center menu-click">
|
||||
<fa-icon class="menu-click" [icon]="['fas', item.faIcon]" [fixedWidth]="true"></fa-icon>
|
||||
<button *ngIf="item.link === 'logout'" class="btn nav-link menu-click" role="tab" (click)="logout()">{{ item.title }}</button>
|
||||
<a *ngIf="item.title !== 'Logout'" class="nav-link menu-click" [routerLink]="[item.link]" role="tab">{{ item.title }}</a>
|
||||
<a *ngIf="item.title !== 'Logout'" class="nav-link menu-click" [routerLink]="[item.link]" role="tab">
|
||||
{{ item.title }}
|
||||
@if (item.isExternal === true) {
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="margin-left: 5px; font-size: 13px; color: lightgray"></fa-icon>
|
||||
}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
ngbTooltip="Miners Reward" placement="bottom" #minersreward [disableTooltip]="!isEllipsisActive(minersreward)">Miners Reward</h5>
|
||||
<div class="card-text" i18n-ngbTooltip="mining.rewards-desc" ngbTooltip="Amount being paid to miners in the past 144 blocks" placement="bottom">
|
||||
<div class="fee-text">
|
||||
<app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
|
||||
<app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true" [ignoreViewMode]="true"></app-amount>
|
||||
</div>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="rewardStats.totalReward" digitsInfo="1.0-0" ></app-fiat>
|
||||
|
||||
@@ -35,6 +35,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||
@Input() minUnit: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' = 'second';
|
||||
@Input() fractionDigits: number = 0;
|
||||
@Input() lowercaseStart = false;
|
||||
|
||||
constructor(
|
||||
private ref: ChangeDetectorRef,
|
||||
@@ -106,6 +107,9 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return $localize`:@@date-base.immediately:Immediately`;
|
||||
} else if (seconds < 60) {
|
||||
if (this.relative || this.kind === 'since') {
|
||||
if (this.lowercaseStart) {
|
||||
return $localize`:@@date-base.just-now:Just now`.charAt(0).toLowerCase() + $localize`:@@date-base.just-now:Just now`.slice(1);
|
||||
}
|
||||
return $localize`:@@date-base.just-now:Just now`;
|
||||
} else if (this.kind === 'until' || this.kind === 'within') {
|
||||
seconds = 60;
|
||||
|
||||
@@ -117,68 +117,71 @@
|
||||
<div class="spinner-border text-light" style="width: 1em; height: 1em"></div>
|
||||
</div>
|
||||
<span class="explainer"> </span>
|
||||
} @else if (showAccelerationSummary) {
|
||||
<ng-container *ngIf="(ETA$ | async) as eta;">
|
||||
<app-accelerate-checkout
|
||||
*ngIf="(da$ | async) as da;"
|
||||
[cashappEnabled]="cashappEligible"
|
||||
[advancedEnabled]="false"
|
||||
[forceMobile]="true"
|
||||
[tx]="tx"
|
||||
[accelerating]="isAcceleration"
|
||||
[miningStats]="miningStats"
|
||||
[eta]="eta"
|
||||
[scrollEvent]="scrollIntoAccelPreview"
|
||||
(unavailable)="eligibleForAcceleration = false"
|
||||
class="h-100 w-100"
|
||||
></app-accelerate-checkout>
|
||||
</ng-container>
|
||||
} @else {
|
||||
@if (tx?.acceleration && !tx.status?.confirmed) {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'wand-magic-sparkles']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.accelerated">Your transaction has been accelerated</span>
|
||||
} @else {
|
||||
@switch (trackerStage) {
|
||||
@case ('waiting') {
|
||||
<div class="progress-icon">
|
||||
<div class="spinner-border text-light" style="width: 1em; height: 1em"></div>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.waiting">Waiting for your transaction to appear in the mempool</span>
|
||||
}
|
||||
@case ('pending') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'hourglass-start']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.pending">Your transaction is in the mempool, but it will not be confirmed for some time.</span>
|
||||
}
|
||||
@case ('soon') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'hourglass-half']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.soon">Your transaction is near the top of the mempool, and is expected to confirm soon.</span>
|
||||
}
|
||||
@case ('next') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'hourglass-end']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.next-block">Your transaction is expected to confirm in the next block</span>
|
||||
}
|
||||
@case ('confirmed') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'circle-check']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.confirmed">Your transaction is confirmed!</span>
|
||||
}
|
||||
@case ('replaced') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'timeline']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.replaced">Your transaction has been replaced by a newer version!</span>
|
||||
@if (!tx.status?.confirmed && showAccelerationSummary) {
|
||||
<ng-container *ngIf="(ETA$ | async) as eta;">
|
||||
<app-accelerate-checkout
|
||||
*ngIf="(da$ | async) as da;"
|
||||
[cashappEnabled]="cashappEligible"
|
||||
[advancedEnabled]="false"
|
||||
[forceMobile]="true"
|
||||
[tx]="tx"
|
||||
[accelerating]="isAcceleration"
|
||||
[miningStats]="miningStats"
|
||||
[eta]="eta"
|
||||
[scrollEvent]="scrollIntoAccelPreview"
|
||||
(unavailable)="eligibleForAcceleration = false"
|
||||
class="w-100"
|
||||
></app-accelerate-checkout>
|
||||
</ng-container>
|
||||
}
|
||||
<div class="status-panel d-flex flex-column h-100 w-100 justify-content-center align-items-center" [class.small-status]="!tx.status?.confirmed && showAccelerationSummary">
|
||||
@if (tx?.acceleration && !tx.status?.confirmed) {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'wand-magic-sparkles']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.accelerated">Your transaction has been accelerated</span>
|
||||
} @else {
|
||||
@switch (trackerStage) {
|
||||
@case ('waiting') {
|
||||
<div class="progress-icon">
|
||||
<div class="spinner-border text-light" style="width: 1em; height: 1em"></div>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.waiting">Waiting for your transaction to appear in the mempool</span>
|
||||
}
|
||||
@case ('pending') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'hourglass-start']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.pending">Your transaction is in the mempool, but it will not be confirmed for some time.</span>
|
||||
}
|
||||
@case ('soon') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'hourglass-half']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.soon">Your transaction is near the top of the mempool, and is expected to confirm soon.</span>
|
||||
}
|
||||
@case ('next') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'hourglass-end']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.next-block">Your transaction is expected to confirm in the next block</span>
|
||||
}
|
||||
@case ('confirmed') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'circle-check']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.confirmed">Your transaction is confirmed!</span>
|
||||
}
|
||||
@case ('replaced') {
|
||||
<div class="progress-icon">
|
||||
<fa-icon [icon]="['fas', 'timeline']" [fixedWidth]="true"></fa-icon>
|
||||
</div>
|
||||
<span class="explainer" i18n="tracker.explain.replaced">Your transaction has been replaced by a newer version!</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: start;
|
||||
|
||||
.explainer {
|
||||
margin: 0 1em;
|
||||
@@ -178,6 +178,15 @@
|
||||
font-size: clamp(30px, 20vw, 150px);
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
&.small-status {
|
||||
min-height: 180px;
|
||||
.progress-icon {
|
||||
font-size: clamp(20px, 13vw, 100px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -596,6 +596,9 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
||||
this.isAccelerated$,
|
||||
this.txChanged$,
|
||||
]).pipe(
|
||||
filter(([position, mempoolBlocks, da, isAccelerated]) => {
|
||||
return this.tx && !this.tx?.status?.confirmed && position && this.tx.txid === position.txid;
|
||||
}),
|
||||
map(([position, mempoolBlocks, da, isAccelerated]) => {
|
||||
return this.etaService.calculateETA(
|
||||
this.network,
|
||||
|
||||
@@ -481,7 +481,7 @@
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td><i><app-time kind="since" [time]="transactionTime" [fastRender]="true" [showTooltip]="true"></app-time></i></td>
|
||||
</tr>
|
||||
} @else {
|
||||
} @else if (isLoadingFirstSeen) {
|
||||
<tr>
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
@@ -663,7 +663,7 @@
|
||||
<ng-template #acceleratingRow>
|
||||
<tr>
|
||||
<td rowspan="2" colspan="2" style="padding: 0;">
|
||||
<app-active-acceleration-box [tx]="tx" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats" [chartPositionLeft]="isMobile"></app-active-acceleration-box>
|
||||
<app-active-acceleration-box [tx]="tx" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats" [hasCpfp]="hasCpfp" (toggleCpfp)="showCpfpDetails = !showCpfpDetails" [chartPositionLeft]="isMobile"></app-active-acceleration-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
|
||||
@@ -42,7 +42,7 @@ interface Pool {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface AuditStatus {
|
||||
export interface TxAuditStatus {
|
||||
seen?: boolean;
|
||||
expected?: boolean;
|
||||
added?: boolean;
|
||||
@@ -100,7 +100,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
sigops: number | null;
|
||||
adjustedVsize: number | null;
|
||||
pool: Pool | null;
|
||||
auditStatus: AuditStatus | null;
|
||||
auditStatus: TxAuditStatus | null;
|
||||
isAcceleration: boolean = false;
|
||||
filters: Filter[] = [];
|
||||
showCpfpDetails = false;
|
||||
@@ -133,6 +133,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
isMobile: boolean;
|
||||
firstLoad = true;
|
||||
waitingForAccelerationInfo: boolean = false;
|
||||
isLoadingFirstSeen = false;
|
||||
|
||||
featuresEnabled: boolean;
|
||||
segwitEnabled: boolean;
|
||||
@@ -374,33 +375,41 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
const auditAvailable = this.isAuditAvailable(height);
|
||||
const isCoinbase = this.tx.vin.some(v => v.is_coinbase);
|
||||
const fetchAudit = auditAvailable && !isCoinbase;
|
||||
return fetchAudit ? this.apiService.getBlockAudit$(hash).pipe(
|
||||
map(audit => {
|
||||
const isAdded = audit.addedTxs.includes(txid);
|
||||
const isPrioritized = audit.prioritizedTxs.includes(txid);
|
||||
const isAccelerated = audit.acceleratedTxs.includes(txid);
|
||||
const isConflict = audit.fullrbfTxs.includes(txid);
|
||||
const isExpected = audit.template.some(tx => tx.txid === txid);
|
||||
const firstSeen = audit.template.find(tx => tx.txid === txid)?.time;
|
||||
return {
|
||||
seen: isExpected || isPrioritized || isAccelerated,
|
||||
expected: isExpected,
|
||||
added: isAdded,
|
||||
prioritized: isPrioritized,
|
||||
conflict: isConflict,
|
||||
accelerated: isAccelerated,
|
||||
firstSeen,
|
||||
};
|
||||
}),
|
||||
retry({ count: 3, delay: 2000 }),
|
||||
catchError(() => {
|
||||
return of(null);
|
||||
})
|
||||
) : of(isCoinbase ? { coinbase: true } : null);
|
||||
if (fetchAudit) {
|
||||
// If block audit is already cached, use it to get transaction audit
|
||||
const blockAuditLoaded = this.apiService.getBlockAuditLoaded(hash);
|
||||
if (blockAuditLoaded) {
|
||||
return this.apiService.getBlockAudit$(hash).pipe(
|
||||
map(audit => {
|
||||
const isAdded = audit.addedTxs.includes(txid);
|
||||
const isPrioritized = audit.prioritizedTxs.includes(txid);
|
||||
const isAccelerated = audit.acceleratedTxs.includes(txid);
|
||||
const isConflict = audit.fullrbfTxs.includes(txid);
|
||||
const isExpected = audit.template.some(tx => tx.txid === txid);
|
||||
const firstSeen = audit.template.find(tx => tx.txid === txid)?.time;
|
||||
return {
|
||||
seen: isExpected || isPrioritized || isAccelerated,
|
||||
expected: isExpected,
|
||||
added: isAdded,
|
||||
prioritized: isPrioritized,
|
||||
conflict: isConflict,
|
||||
accelerated: isAccelerated,
|
||||
firstSeen,
|
||||
};
|
||||
})
|
||||
)
|
||||
} else {
|
||||
return this.apiService.getBlockTxAudit$(hash, txid).pipe(
|
||||
retry({ count: 3, delay: 2000 }),
|
||||
catchError(() => {
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return of(isCoinbase ? { coinbase: true } : null);
|
||||
}
|
||||
}),
|
||||
catchError((e) => {
|
||||
return of(null);
|
||||
})
|
||||
).subscribe(auditStatus => {
|
||||
this.auditStatus = auditStatus;
|
||||
if (this.auditStatus?.firstSeen) {
|
||||
@@ -755,8 +764,16 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
getTransactionTime() {
|
||||
this.isLoadingFirstSeen = true;
|
||||
this.apiService
|
||||
.getTransactionTimes$([this.tx.txid])
|
||||
.pipe(
|
||||
retry({ count: 2, delay: 2000 }),
|
||||
catchError(() => {
|
||||
this.isLoadingFirstSeen = false;
|
||||
return throwError(() => new Error(''));
|
||||
})
|
||||
)
|
||||
.subscribe((transactionTimes) => {
|
||||
if (transactionTimes?.length && transactionTimes[0]) {
|
||||
this.transactionTime = transactionTimes[0];
|
||||
|
||||
@@ -272,11 +272,17 @@
|
||||
<ng-template #mempoolTable let-mempoolInfoData>
|
||||
<div class="mempool-info-data">
|
||||
<div class="item">
|
||||
<h5 *ngIf="!mempoolInfoData.value || mempoolInfoData.value.memPoolInfo.mempoolminfee === mempoolInfoData.value.memPoolInfo.minrelaytxfee || (stateService.env.BASE_MODULE === 'liquid' && mempoolInfoData.value.memPoolInfo.mempoolminfee === 0.000001) else purgingText" class="card-title" i18n="dashboard.minimum-fee|Minimum mempool fee">Minimum fee</h5>
|
||||
<ng-template #purgingText><h5 class="card-title" i18n="dashboard.purging|Purgin below fee">Purging</h5></ng-template>
|
||||
<p class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loading">
|
||||
<ng-template [ngIf]="mempoolInfoData.value.memPoolInfo.mempoolminfee !== mempoolInfoData.value.memPoolInfo.minrelaytxfee">< </ng-template><app-fee-rate [fee]="mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000"></app-fee-rate>
|
||||
</p>
|
||||
@if (!mempoolInfoData.value || mempoolInfoData.value.memPoolInfo.mempoolminfee === mempoolInfoData.value.memPoolInfo.minrelaytxfee || (stateService.env.BASE_MODULE === 'liquid' && mempoolInfoData.value.memPoolInfo.mempoolminfee === 0.000001)) {
|
||||
<h5 class="card-title" i18n="dashboard.minimum-fee|Minimum mempool fee">Minimum fee</h5>
|
||||
<p class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loading">
|
||||
<app-fee-rate [fee]="mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000"></app-fee-rate>
|
||||
</p>
|
||||
} @else {
|
||||
<h5 class="card-title" i18n="dashboard.purging|Purging below fee">Purging</h5>
|
||||
<p class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loading" i18n-ngbTooltip="dashboard.purging-desc" ngbTooltip="Fee rate below which transactions are purged from default Bitcoin Core nodes" placement="bottom">
|
||||
< <app-fee-rate [fee]="mempoolInfoData.value.memPoolInfo.mempoolminfee * 100000"></app-fee-rate>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="dashboard.unconfirmed|Unconfirmed count">Unconfirmed</h5>
|
||||
@@ -287,7 +293,7 @@
|
||||
<div class="item bar">
|
||||
<h5 class="card-title" i18n="dashboard.memory-usage|Memory usage">Memory Usage</h5>
|
||||
<div class="card-text" *ngIf="(isLoadingWebSocket$ | async) === false && mempoolInfoData.value; else loadingbig">
|
||||
<div class="progress">
|
||||
<div class="progress" i18n-ngbTooltip="dashboard.memory-usage-desc" ngbTooltip="Memory used by our mempool (may exceed default Bitcoin Core limit)" placement="bottom">
|
||||
<div class="progress-bar {{ mempoolInfoData.value.mempoolSizeProgress }}" role="progressbar" [ngStyle]="{'width': (mempoolInfoData.value.memPoolInfo.usage / mempoolInfoData.value.memPoolInfo.maxmempool * 100) + '%' }"> </div>
|
||||
<div class="progress-text">‎<span [innerHTML]="mempoolInfoData.value.memPoolInfo.usage | bytes : 2 : 'B' : null : false : 3"></span> / <span [innerHTML]="mempoolInfoData.value.memPoolInfo.maxmempool | bytes"></span></div>
|
||||
</div>
|
||||
|
||||
@@ -9017,18 +9017,89 @@ export const restApiDocsData = [
|
||||
"effectiveFee": 154,
|
||||
"ancestorCount": 1
|
||||
},
|
||||
"cost": 3850,
|
||||
"targetFeeRate": 26,
|
||||
"nextBlockFee": 4004,
|
||||
"userBalance": 99900000,
|
||||
"mempoolBaseFee": 40000,
|
||||
"vsizeFee": 50000,
|
||||
"hasAccess": true
|
||||
"cost": 1386,
|
||||
"targetFeeRate": 10,
|
||||
"nextBlockFee": 1540,
|
||||
"userBalance": 0,
|
||||
"mempoolBaseFee": 50000,
|
||||
"vsizeFee": 0,
|
||||
"pools": [
|
||||
111,
|
||||
102,
|
||||
112,
|
||||
142,
|
||||
115
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"fee": 1500
|
||||
},
|
||||
{
|
||||
"fee": 3000
|
||||
},
|
||||
{
|
||||
"fee": 12500
|
||||
}
|
||||
],
|
||||
"hasAccess": false,
|
||||
"availablePaymentMethods": {
|
||||
"bitcoin": {
|
||||
"enabled": true,
|
||||
"min": 1000,
|
||||
"max": 10000000
|
||||
},
|
||||
"cashapp": {
|
||||
"enabled": true,
|
||||
"min": 10,
|
||||
"max": 200
|
||||
}
|
||||
},
|
||||
"unavailable": false
|
||||
}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
options: { officialOnly: true },
|
||||
type: "endpoint",
|
||||
category: "accelerator-public",
|
||||
httpRequestMethod: "POST",
|
||||
fragment: "accelerator-get-invoice",
|
||||
title: "POST Generate Acceleration Invoice",
|
||||
description: {
|
||||
default: "<p>Request a LN invoice to accelerate a transaction.</p>"
|
||||
},
|
||||
urlString: "/v1/services/payments/bitcoin",
|
||||
showConditions: [""],
|
||||
showJsExamples: showJsExamplesDefaultFalse,
|
||||
codeExample: {
|
||||
default: {
|
||||
codeTemplate: {
|
||||
curl: `%{1}" "[[hostname]][[baseNetworkUrl]]/api/v1/services/payments/bitcoin`, //custom interpolation technique handled in replaceCurlPlaceholder()
|
||||
commonJS: ``,
|
||||
esModule: ``
|
||||
},
|
||||
codeSampleMainnet: {
|
||||
esModule: [],
|
||||
commonJS: [],
|
||||
curl: ["product=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29&amount=12500"],
|
||||
headers: "",
|
||||
response: `[
|
||||
{
|
||||
"btcpayInvoiceId": "4Ww53d7VgSa596jmCFufe7",
|
||||
"btcDue": "0.000625",
|
||||
"addresses": {
|
||||
"BTC": "bc1qcvqx2kr5mktd7gvym0atrrx0sn27mwv5kkghl3m78kegndm5t8ksvcqpja",
|
||||
"BTC_LNURLPAY": null,
|
||||
"BTC_LightningLike": "lnbc625u1pngl0wzpp56j7cqghsw2y5q7vdu9shmpxgpzsx4pqra4wcm9vdnvqegutplk2qdxj2pskjepqw3hjqnt9d4cx7mmvypqkxcm9d3jhyct5daezq2z0wfjx2u3qf9zr5grpvd3k2mr9wfshg6t0dckk2ef3xdjkyc3e8ymrxv3nxumkxvf4vvungwfcxqen2dmxxcmngepj8q6kzce5xyengdfjxq6nqvpnx9jkzdnyvdjrxefevgexgcej8yknzdejxqmrjd3jx5mrgdpj9ycqzpuxqrpr5sp58593dzj2uauaj3afa7x47qeam8k9yyqrh9qasj2ssdzstew6qv3q9qxpqysgqj8qshfkxmj0gfkly5xfydysvsx55uhnc6fgpw66uf6hl8leu07454axe2kq0q788yysg8guel2r36d6f75546nkhmdcmec4mmlft8dsq62rnsj"
|
||||
}
|
||||
}
|
||||
]`,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
options: { officialOnly: true },
|
||||
type: "endpoint",
|
||||
@@ -9119,14 +9190,18 @@ export const restApiDocsData = [
|
||||
"txid": "d7e1796d8eb4a09d4e6c174e36cfd852f1e6e6c9f7df4496339933cd32cbdd1d",
|
||||
"status": "completed",
|
||||
"added": 1707421053,
|
||||
"lastUpdated": 1707422952,
|
||||
"lastUpdated": 1719134667,
|
||||
"effectiveFee": 146,
|
||||
"effectiveVsize": 141,
|
||||
"feeDelta": 14000,
|
||||
"blockHash": "00000000000000000000482f0746d62141694b9210a813b97eb8445780a32003",
|
||||
"blockHeight": 829559,
|
||||
"bidBoost": 6102,
|
||||
"pools": [111]
|
||||
"bidBoost": 3239,
|
||||
"boostVersion": "v1",
|
||||
"pools": [
|
||||
111
|
||||
],
|
||||
"minedByPoolUniqueId": 111
|
||||
}
|
||||
]`,
|
||||
},
|
||||
@@ -9229,7 +9304,7 @@ export const restApiDocsData = [
|
||||
category: "accelerator-private",
|
||||
httpRequestMethod: "POST",
|
||||
fragment: "accelerator-accelerate",
|
||||
title: "POST Accelerate A Transaction",
|
||||
title: "POST Accelerate A Transaction (Pro)",
|
||||
description: {
|
||||
default: "<p>Sends a request to accelerate a transaction.</p>"
|
||||
},
|
||||
|
||||
@@ -50,7 +50,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'acceleration',
|
||||
data: { networks: ['bitcoin'] },
|
||||
data: { networks: ['bitcoin'], networkSpecific: true, onlySubnet: [''] },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
@@ -61,7 +61,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'acceleration/list/:page',
|
||||
data: { networks: ['bitcoin'] },
|
||||
data: { networks: ['bitcoin'], networkSpecific: true, onlySubnet: [''] },
|
||||
component: AccelerationsListComponent,
|
||||
},
|
||||
{
|
||||
@@ -140,7 +140,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'acceleration/fees',
|
||||
data: { networks: ['bitcoin'] },
|
||||
data: { networks: ['bitcoin'], networkSpecific: true, onlySubnet: [''] },
|
||||
component: AccelerationFeesGraphComponent,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -411,7 +411,7 @@ export interface Acceleration {
|
||||
}
|
||||
|
||||
export interface AccelerationHistoryParams {
|
||||
status?: string;
|
||||
status?: string; // Single status or comma separated list of status
|
||||
timeframe?: string;
|
||||
poolUniqueId?: number;
|
||||
blockHash?: string;
|
||||
|
||||
@@ -5,6 +5,7 @@ export type MenuItem = {
|
||||
i18n: string;
|
||||
faIcon: IconName;
|
||||
link: string;
|
||||
isExternal?: boolean;
|
||||
};
|
||||
export type MenuGroup = {
|
||||
title: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.dashboard-container {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 18px;
|
||||
@media (min-width: 992px) {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { Conversion } from './price.service';
|
||||
import { StorageService } from './storage.service';
|
||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||
import { TxAuditStatus } from '../components/transaction/transaction.component';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -17,6 +18,7 @@ export class ApiService {
|
||||
private apiBasePath: string; // network path is /testnet, etc. or '' for mainnet
|
||||
|
||||
private requestCache = new Map<string, { subject: BehaviorSubject<any>, expiry: number }>;
|
||||
public blockAuditLoaded: { [hash: string]: boolean } = {};
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
@@ -369,11 +371,18 @@ export class ApiService {
|
||||
}
|
||||
|
||||
getBlockAudit$(hash: string) : Observable<BlockAudit> {
|
||||
this.setBlockAuditLoaded(hash);
|
||||
return this.httpClient.get<BlockAudit>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/block/${hash}/audit-summary`
|
||||
);
|
||||
}
|
||||
|
||||
getBlockTxAudit$(hash: string, txid: string) : Observable<TxAuditStatus> {
|
||||
return this.httpClient.get<TxAuditStatus>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/block/${hash}/tx/${txid}/audit`
|
||||
);
|
||||
}
|
||||
|
||||
getBlockAuditScores$(from: number): Observable<AuditScore[]> {
|
||||
return this.httpClient.get<AuditScore[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/audit/scores` +
|
||||
@@ -526,4 +535,13 @@ export class ApiService {
|
||||
this.apiBaseUrl + this.apiBasePath + '/api/v1/accelerations/total' + (queryString?.length ? '?' + queryString : '')
|
||||
);
|
||||
}
|
||||
|
||||
// Cache methods
|
||||
async setBlockAuditLoaded(hash: string) {
|
||||
this.blockAuditLoaded[hash] = true;
|
||||
}
|
||||
|
||||
getBlockAuditLoaded(hash) {
|
||||
return this.blockAuditLoaded[hash];
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,7 @@ export class CacheService {
|
||||
resetBlockCache() {
|
||||
this.blockHashCache = {};
|
||||
this.blockCache = {};
|
||||
this.apiService.blockAuditLoaded = {};
|
||||
this.blockLoading = {};
|
||||
this.copiesInBlockQueue = {};
|
||||
this.blockPriorities = [];
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Router, NavigationEnd, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { StateService } from './state.service';
|
||||
import { RelativeUrlPipe } from '../shared/pipes/relative-url/relative-url.pipe';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -30,15 +31,30 @@ export class NavigationService {
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private router: Router,
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
) {
|
||||
this.router.events.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
map(() => this.router.routerState.snapshot.root),
|
||||
).subscribe((state) => {
|
||||
this.updateSubnetPaths(state);
|
||||
if (this.enforceSubnetRestrictions(state)) {
|
||||
this.updateSubnetPaths(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
enforceSubnetRestrictions(root: ActivatedRouteSnapshot): boolean {
|
||||
let route = root;
|
||||
while (route) {
|
||||
if (route.data.onlySubnet && !route.data.onlySubnet.includes(this.stateService.network)) {
|
||||
this.router.navigate([this.relativeUrlPipe.transform('')]);
|
||||
return false;
|
||||
}
|
||||
route = route.firstChild;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// For each network (bitcoin/liquid), find and save the longest url path compatible with the current route
|
||||
updateSubnetPaths(root: ActivatedRouteSnapshot): void {
|
||||
let path = '';
|
||||
|
||||
@@ -181,7 +181,7 @@ export class ServicesApiServices {
|
||||
return this.httpClient.get<any[]>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/invoice?id=${invoiceId}`);
|
||||
}
|
||||
|
||||
getPaymentStatus$(orderId: string): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/check?order_id=${orderId}`);
|
||||
getPaymentStatus$(orderId: string): Observable<any> {
|
||||
return this.httpClient.get<any>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/check?order_id=${orderId}`, { observe: 'response' });
|
||||
}
|
||||
}
|
||||
+382
-326
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+386
-330
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+383
-327
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+381
-325
File diff suppressed because it is too large.
Load diff
+383
-327
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+383
-327
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+383
-327
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+404
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+391
-332
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+385
-329
File diff suppressed because it is too large.
Load diff
+382
-326
File diff suppressed because it is too large.
Load diff
+375
-315
File diff suppressed because it is too large.
Load diff
+386
-331
File diff suppressed because it is too large.
Load diff
@@ -64,6 +64,57 @@ location @mempool-api-v1-services-cache-disabled {
|
||||
expires -1;
|
||||
}
|
||||
|
||||
location @mempool-api-v1-services-cache-disabled-addcors {
|
||||
proxy_pass $mempoolSpaceServices;
|
||||
|
||||
# remove these just in case double proxied
|
||||
proxy_hide_header Onion-Location;
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
|
||||
# remove cache headers from services backend
|
||||
proxy_hide_header Cache-Control;
|
||||
proxy_hide_header Expires;
|
||||
proxy_hide_header Pragma;
|
||||
|
||||
# pass these headers to services backend
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# add our own cache headers
|
||||
add_header 'Pragma' 'no-cache';
|
||||
add_header 'Cache-Control' 'private, no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
|
||||
|
||||
# generate CORS configuration from origin header
|
||||
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
set $cors_origin 'https://mempool.space';
|
||||
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
|
||||
set $cors_credentials 'true';
|
||||
|
||||
# set CORS for approved hostnames
|
||||
if ($cors_approved_origin = 'legit')
|
||||
{
|
||||
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
set $cors_origin "$http_origin";
|
||||
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
|
||||
set $cors_credentials 'true';
|
||||
}
|
||||
|
||||
# set CORS for approved hostnames
|
||||
add_header Access-Control-Allow-Methods "$cors_methods" always;
|
||||
add_header Access-Control-Allow-Origin "$cors_origin" always;
|
||||
add_header Access-Control-Allow-Headers "$cors_headers" always;
|
||||
add_header Access-Control-Allow-Credentials "$cors_credentials" always;
|
||||
|
||||
proxy_redirect off;
|
||||
proxy_buffering off;
|
||||
|
||||
expires -1;
|
||||
}
|
||||
|
||||
location @mempool-api-v1-services-cache-short {
|
||||
proxy_pass $mempoolSpaceServices;
|
||||
proxy_cache services;
|
||||
@@ -95,6 +146,58 @@ location @mempool-api-v1-services-cache-short {
|
||||
proxy_cache_valid 200 2s;
|
||||
}
|
||||
|
||||
location @mempool-api-v1-services-cache-short-addcors {
|
||||
proxy_pass $mempoolSpaceServices;
|
||||
proxy_cache services;
|
||||
proxy_redirect off;
|
||||
|
||||
# remove these just in case double proxied
|
||||
proxy_hide_header Onion-Location;
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
|
||||
# pass these headers to services backend
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# remove cache headers from services backend
|
||||
proxy_hide_header Cache-Control;
|
||||
proxy_hide_header Expires;
|
||||
proxy_hide_header Pragma;
|
||||
|
||||
# generate CORS configuration from origin header
|
||||
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
set $cors_origin 'https://mempool.space';
|
||||
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
|
||||
set $cors_credentials 'true';
|
||||
|
||||
# set CORS for approved hostnames
|
||||
if ($cors_approved_origin = 'legit')
|
||||
{
|
||||
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
set $cors_origin "$http_origin";
|
||||
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
|
||||
set $cors_credentials 'true';
|
||||
}
|
||||
|
||||
# set CORS for approved hostnames
|
||||
add_header Access-Control-Allow-Methods "$cors_methods" always;
|
||||
add_header Access-Control-Allow-Origin "$cors_origin" always;
|
||||
add_header Access-Control-Allow-Headers "$cors_headers" always;
|
||||
add_header Access-Control-Allow-Credentials "$cors_credentials" always;
|
||||
|
||||
# add our own cache headers
|
||||
add_header 'Pragma' 'public';
|
||||
add_header 'Cache-Control' 'public';
|
||||
expires 2s;
|
||||
|
||||
# server-side cache validity
|
||||
proxy_cache_valid 200 2s;
|
||||
}
|
||||
|
||||
location @mempool-api-v1-services-cache-medium {
|
||||
proxy_pass $mempoolSpaceServices;
|
||||
proxy_cache services;
|
||||
|
||||
@@ -47,9 +47,6 @@ class Server {
|
||||
case "liquid":
|
||||
canonical = "https://liquid.network"
|
||||
break;
|
||||
case "bisq":
|
||||
canonical = "https://bisq.markets"
|
||||
break;
|
||||
case "onbtc":
|
||||
canonical = "https://bitcoin.gob.sv"
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user