From 9e88f5ecf8c591b0046e454d186cd8f626d83842 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Fri, 15 Dec 2023 16:58:10 +0100 Subject: [PATCH 01/18] [typo] platinium -> platinum --- frontend/src/app/components/menu/menu.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/menu/menu.component.scss b/frontend/src/app/components/menu/menu.component.scss index 8a2006a4e..99377a564 100644 --- a/frontend/src/app/components/menu/menu.component.scss +++ b/frontend/src/app/components/menu/menu.component.scss @@ -85,6 +85,6 @@ background-color: #f1c40f; } -.badge-platinium { +.badge-platinum { background-color: #653b9c; } \ No newline at end of file From ea1b74dcefe56cc816d0fce79347eeeafd494768 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 27 Jan 2024 19:35:16 +0100 Subject: [PATCH 02/18] [doc] add accelerator rest api documentation --- .../src/app/docs/api-docs/api-docs-data.ts | 285 ++++++++++++++++++ .../app/docs/api-docs/api-docs.component.html | 88 +++--- .../code-template/code-template.component.ts | 16 +- 3 files changed, 339 insertions(+), 50 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 86a63e513..8998ec59a 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -9871,7 +9871,292 @@ export const restApiDocsData = [ codeSampleBisq: emptyCodeSample, } } + }, + { + type: "category", + category: "accelerator", + fragment: "accelerator", + title: "Accelerator", + showConditions: [""], + options: { officialOnly: true }, + }, + { + options: { officialOnly: true }, + type: "endpoint", + category: "accelerator", + httpRequestMethod: "GET", + fragment: "get-deposit-history", + title: "GET Deposit History", + description: { + default: "

Returns a list of previous deposits made as prepayment for the accelerator service.

" + }, + urlString: "/v1/services/accelerator/deposit-history", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/services/accelerator/deposit-history`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + headers: "api_key: stacksats", + response: `[ + { + "type": "Bitcoin", + "invoiceId": "CCunucVyNw7jUiUz64mmHz", + "amount": 10311031, + "status": "pending", + "date": 1706372653000, + "link": "/payment/bitcoin/CCunucVyNw7jUiUz64mmHz" + }, + { + "type": "Bitcoin", + "invoiceId": "SG1U27R9PdWi3gH3jB9tm9", + "amount": 21000000, + "status": "paid", + "date": 1706372582000, + "link": null + }, + ... +]`, + }, + } + } + }, + { + options: { officialOnly: true }, + type: "endpoint", + category: "accelerator", + httpRequestMethod: "GET", + fragment: "balance", + title: "GET Available Balance", + description: { + default: "

Return your current available balance, as well as how much funds are currently locked and much you've been charged so far.

" + }, + urlString: "/v1/services/accelerator/balance", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/services/accelerator/balance`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + headers: "api_key: stacksats", + response: `{ + "balance": 99900000, + "hold": 0, + "feesPaid": 200000 +}`, + }, + } + } + }, + { + options: { officialOnly: true }, + type: "endpoint", + category: "accelerator", + httpRequestMethod: "POST", + fragment: "estimate", + title: "POST Calculate Estimated Costs", + description: { + default: "

Return an estimation of the costs to accelerate a transaction.

" + }, + urlString: "/v1/services/accelerator/estimate", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `%{1}" "[[hostname]][[baseNetworkUrl]]/api/v1/services/accelerator/estimate`, //custom interpolation technique handled in replaceCurlPlaceholder() + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29"], + headers: "api_key: stacksats", + response: `{ + "txSummary": { + "txid": "ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29", + "effectiveVsize": 154, + "effectiveFee": 154, + "ancestorCount": 1 + }, + "cost": 3850, + "targetFeeRate": 26, + "nextBlockFee": 4004, + "userBalance": 99900000, + "mempoolBaseFee": 40000, + "vsizeFee": 50000, + "hasAccess": true +}`, + }, + } + } + }, + { + options: { officialOnly: true }, + type: "endpoint", + category: "accelerator", + httpRequestMethod: "POST", + fragment: "accelerate", + title: "POST Accelerate A Transaction", + description: { + default: "

Send a request to accelerate a transaction.

" + }, + urlString: "/v1/services/accelerator/accelerate", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `%{1}" "[[hostname]][[baseNetworkUrl]]/api/v1/services/accelerator/accelerate`, //custom interpolation technique handled in replaceCurlPlaceholder() + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: ["txInput=ee13ebb99632377c15c94980357f674d285ac413452050031ea6dcd3e9b2dc29&userBid=21000000"], + headers: "api_key: stacksats", + response: `HTTP/1.1 200 OK`, + }, + } + } + }, + { + options: { officialOnly: true }, + type: "endpoint", + category: "history", + httpRequestMethod: "GET", + fragment: "history", + title: "GET Acceleration History", + description: { + default: "

Return the history of previous acceleration requests.

" + }, + urlString: "/v1/services/accelerator/history[?status=all|requested|accelerating|mined|completed|failed]", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/services/accelerator/history?status=all`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + headers: "api_key: stacksats", + response: `[ + { + "id": 89, + "user_id": 1, + "txid": "ae2639469ec000ed1d14e2550cbb01794e1cd288a00cdc7cce18398ba3cc2ffe", + "status": "failed", + "estimated_fee": 247, + "fee_paid": 0, + "added": 1706378712, + "last_updated": 1706378712, + "confirmations": 4, + "base_fee": 0, + "vsize_fee": 0, + "max_bid": 7000, + "effective_vsize": 135, + "effective_fee": 3128, + "history": [ + { + "event": "user-requested-acceleration", + "timestamp": 1706378712 + }, + { + "event": "accepted_test-api-key", + "timestamp": 1706378712 + }, + { + "event": "failed-at-block-827672", + "timestamp": 1706380261 + } + ] + }, + { + "id": 88, + "user_id": 1, + "txid": "c5840e89173331760e959a190b24e2a289121277ed7f8a095fe289b37cee9fde", + "status": "completed", + "estimated_fee": 223, + "fee_paid": 140019, + "added": 1706378704, + "last_updated": 1706380231, + "confirmations": 6, + "base_fee": 40000, + "vsize_fee": 100000, + "max_bid": 14000, + "effective_vsize": 135, + "effective_fee": 3152, + "history": [ + { + "event": "user-requested-acceleration", + "timestamp": 1706378704 + }, + { + "event": "accepted_test-api-key", + "timestamp": 1706378704 + }, + { + "event": "complete-at-block-827670", + "timestamp": 1706380231 + } + ] + }, + { + "id": 87, + "user_id": 1, + "txid": "178b5b9b310f0d667d7ea563a2cdcc17bc8cd15261b58b1653860a724ca83458", + "status": "completed", + "estimated_fee": 115, + "fee_paid": 90062, + "added": 1706378684, + "last_updated": 1706380231, + "confirmations": 6, + "base_fee": 40000, + "vsize_fee": 50000, + "max_bid": 14000, + "effective_vsize": 135, + "effective_fee": 3260, + "history": [ + { + "event": "user-requested-acceleration", + "timestamp": 1706378684 + }, + { + "event": "accepted_test-api-key", + "timestamp": 1706378684 + }, + { + "event": "complete-at-block-827670", + "timestamp": 1706380231 + } + ] } +]`, + }, + } + } + }, ]; export const faqData = [ diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index c3a260995..ef7782199 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -43,54 +43,56 @@

Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an enterprise sponsorship if you need higher API limits.

-

{{ item.title }}

-
- {{ item.title }} {{ item.category }} -
-
-
Endpoint
- - {{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }} +
+

{{ item.title }}

+
+ {{ item.title }} {{ item.category }} +
+
+
Endpoint
+ + {{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }} + + + + {{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }} +

{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}

+
+
+ + + {{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }} +

{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}

+
+
+
{{ item.httpRequestMethod }} {{ item.urlString }}
+
+
+
Description
+ +
+
+ + +
+
+
+ +
+
+
+ + - - - {{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }} -

{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}

+ + + - - - {{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }} -

{{ item.httpRequestMethod }} {{ baseNetworkUrl }}/api{{ item.urlString }}

-
-
-
{{ item.httpRequestMethod }} {{ item.urlString }}
-
-
-
Description
- -
-
- - -
-
-
- -
+ +
- - - - - - - - - - -
diff --git a/frontend/src/app/docs/code-template/code-template.component.ts b/frontend/src/app/docs/code-template/code-template.component.ts index 5ef8a64ba..6b91f5a9d 100644 --- a/frontend/src/app/docs/code-template/code-template.component.ts +++ b/frontend/src/app/docs/code-template/code-template.component.ts @@ -311,27 +311,29 @@ yarn add @mempool/liquid.js`; text = text.replace('%{' + indexNumber + '}', textReplace); } + const headersString = code.headers ? ` -H "${code.headers}"` : ``; + if (this.env.BASE_MODULE === 'mempool') { if (this.network === 'main' || this.network === '') { if (this.method === 'POST') { - return `curl -X POST -sSLd "${text}"`; + return `curl${headersString} -X POST -sSLd "${text}"`; } - return `curl -sSL "${this.hostname}${text}"`; + return `curl${headersString} -sSL "${this.hostname}${text}"`; } if (this.method === 'POST') { - return `curl -X POST -sSLd "${text}"`; + return `curl${headersString} -X POST -sSLd "${text}"`; } - return `curl -sSL "${this.hostname}/${this.network}${text}"`; + return `curl${headersString} -sSL "${this.hostname}/${this.network}${text}"`; } else if (this.env.BASE_MODULE === 'liquid') { if (this.method === 'POST') { if (this.network !== 'liquid') { text = text.replace('/api', `/${this.network}/api`); } - return `curl -X POST -sSLd "${text}"`; + return `curl${headersString} -X POST -sSLd "${text}"`; } - return ( this.network === 'liquid' ? `curl -sSL "${this.hostname}${text}"` : `curl -sSL "${this.hostname}/${this.network}${text}"` ); + return ( this.network === 'liquid' ? `curl${headersString} -sSL "${this.hostname}${text}"` : `curl${headersString} -sSL "${this.hostname}/${this.network}${text}"` ); } else { - return `curl -sSL "${this.hostname}${text}"`; + return `curl${headersString} -sSL "${this.hostname}${text}"`; } } From a3055e20f43252bb3ce49571f42a7f348261e919 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:56:45 -0500 Subject: [PATCH 03/18] Fix signet and testnet docs pages --- frontend/src/app/docs/api-docs/api-docs-data.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 8998ec59a..6b765e5c6 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -9891,7 +9891,7 @@ export const restApiDocsData = [ default: "

Returns a list of previous deposits made as prepayment for the accelerator service.

" }, urlString: "/v1/services/accelerator/deposit-history", - showConditions: bitcoinNetworks, + showConditions: [""], showJsExamples: showJsExamplesDefaultFalse, codeExample: { default: { @@ -9939,7 +9939,7 @@ export const restApiDocsData = [ default: "

Return your current available balance, as well as how much funds are currently locked and much you've been charged so far.

" }, urlString: "/v1/services/accelerator/balance", - showConditions: bitcoinNetworks, + showConditions: [""], showJsExamples: showJsExamplesDefaultFalse, codeExample: { default: { @@ -9973,7 +9973,7 @@ export const restApiDocsData = [ default: "

Return an estimation of the costs to accelerate a transaction.

" }, urlString: "/v1/services/accelerator/estimate", - showConditions: bitcoinNetworks, + showConditions: [""], showJsExamples: showJsExamplesDefaultFalse, codeExample: { default: { @@ -10017,7 +10017,7 @@ export const restApiDocsData = [ default: "

Send a request to accelerate a transaction.

" }, urlString: "/v1/services/accelerator/accelerate", - showConditions: bitcoinNetworks, + showConditions: [""], showJsExamples: showJsExamplesDefaultFalse, codeExample: { default: { @@ -10047,7 +10047,7 @@ export const restApiDocsData = [ default: "

Return the history of previous acceleration requests.

" }, urlString: "/v1/services/accelerator/history[?status=all|requested|accelerating|mined|completed|failed]", - showConditions: bitcoinNetworks, + showConditions: [""], showJsExamples: showJsExamplesDefaultFalse, codeExample: { default: { From b2a130bb17644a700986faea30746172e638c964 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 29 Jan 2024 11:45:14 +0100 Subject: [PATCH 04/18] [doc] fix copy, formatting and hide accelerator doc title if non official instance --- frontend/src/app/docs/api-docs/api-docs-data.ts | 10 +++++----- .../src/app/docs/api-docs/api-docs-nav.component.html | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 6b765e5c6..a0d292925 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -9888,7 +9888,7 @@ export const restApiDocsData = [ fragment: "get-deposit-history", title: "GET Deposit History", description: { - default: "

Returns a list of previous deposits made as prepayment for the accelerator service.

" + default: "

Returns a list of deposits made as prepayment for the accelerator service.

" }, urlString: "/v1/services/accelerator/deposit-history", showConditions: [""], @@ -9936,7 +9936,7 @@ export const restApiDocsData = [ fragment: "balance", title: "GET Available Balance", description: { - default: "

Return your current available balance, as well as how much funds are currently locked and much you've been charged so far.

" + default: "

Returns your currently available balance, currently locked funds, and total fees paid so far.

" }, urlString: "/v1/services/accelerator/balance", showConditions: [""], @@ -9970,7 +9970,7 @@ export const restApiDocsData = [ fragment: "estimate", title: "POST Calculate Estimated Costs", description: { - default: "

Return an estimation of the costs to accelerate a transaction.

" + default: "

Returns estimated costs to accelerate a transaction.

" }, urlString: "/v1/services/accelerator/estimate", showConditions: [""], @@ -10044,9 +10044,9 @@ export const restApiDocsData = [ fragment: "history", title: "GET Acceleration History", description: { - default: "

Return the history of previous acceleration requests.

" + default: "

Return the history of previous acceleration requests.

Pass one of the following for :status: all, requested, accelerating, mined, completed, failed.

" }, - urlString: "/v1/services/accelerator/history[?status=all|requested|accelerating|mined|completed|failed]", + urlString: "/v1/services/accelerator/history?status=:status", showConditions: [""], showJsExamples: showJsExamplesDefaultFalse, codeExample: { diff --git a/frontend/src/app/docs/api-docs/api-docs-nav.component.html b/frontend/src/app/docs/api-docs/api-docs-nav.component.html index ec1cde38f..96622c424 100644 --- a/frontend/src/app/docs/api-docs/api-docs-nav.component.html +++ b/frontend/src/app/docs/api-docs/api-docs-nav.component.html @@ -1,4 +1,4 @@
-

{{ item.title }}

+

{{ item.title }}

{{ item.title }}
From 1121136a5ec85a71d9ad8ee42f43e0f36c6f9bec Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 8 Feb 2024 22:40:22 +0000 Subject: [PATCH 05/18] Reduce the network size of mempool block websocket updates --- backend/src/api/mempool-blocks.ts | 42 ++++++++++++++++--- backend/src/api/websocket-handler.ts | 4 +- backend/src/mempool.interfaces.ts | 9 +++- .../mempool-block-overview.component.ts | 3 +- .../src/app/interfaces/websocket.interface.ts | 17 ++++++-- frontend/src/app/services/state.service.ts | 2 +- .../src/app/services/websocket.service.ts | 5 ++- frontend/src/app/shared/common.utils.ts | 27 ++++++++++++ 8 files changed, 93 insertions(+), 16 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 58921fcfb..b9da7d4e8 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -1,6 +1,6 @@ import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt'; import logger from '../logger'; -import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, PoolTag, TransactionClassified } from '../mempool.interfaces'; +import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, PoolTag, TransactionClassified, TransactionCompressed, MempoolDeltaChange } from '../mempool.interfaces'; import { Common, OnlineFeeStatsCalculator } from './common'; import config from '../config'; import { Worker } from 'worker_threads'; @@ -171,7 +171,7 @@ class MempoolBlocks { for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) { let added: TransactionClassified[] = []; let removed: string[] = []; - const changed: { txid: string, rate: number | undefined, acc: boolean | undefined }[] = []; + const changed: TransactionClassified[] = []; if (mempoolBlocks[i] && !prevBlocks[i]) { added = mempoolBlocks[i].transactions; } else if (!mempoolBlocks[i] && prevBlocks[i]) { @@ -194,14 +194,14 @@ class MempoolBlocks { if (!prevIds[tx.txid]) { added.push(tx); } else if (tx.rate !== prevIds[tx.txid].rate || tx.acc !== prevIds[tx.txid].acc) { - changed.push({ txid: tx.txid, rate: tx.rate, acc: tx.acc }); + changed.push(tx); } }); } mempoolBlockDeltas.push({ - added, + added: added.map(this.compressTx), removed, - changed, + changed: changed.map(this.compressDeltaChange), }); } return mempoolBlockDeltas; @@ -691,6 +691,38 @@ class MempoolBlocks { }); return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow }; } + + public compressTx(tx: TransactionClassified): TransactionCompressed { + if (tx.acc) { + return [ + tx.txid, + tx.fee, + tx.vsize, + tx.value, + Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100, + tx.flags, + 1 + ]; + } else { + return [ + tx.txid, + tx.fee, + tx.vsize, + tx.value, + Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100, + tx.flags, + ]; + } + } + + public compressDeltaChange(tx: TransactionClassified): MempoolDeltaChange { + return [ + tx.txid, + Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100, + tx.flags, + tx.acc ? 1 : 0, + ]; + } } export default new MempoolBlocks(); diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index d0e0b7fd8..3091a09cf 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -259,7 +259,7 @@ class WebsocketHandler { const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions(); response['projected-block-transactions'] = JSON.stringify({ index: index, - blockTransactions: mBlocksWithTransactions[index]?.transactions || [], + blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx), }); } else { client['track-mempool-block'] = null; @@ -999,7 +999,7 @@ class WebsocketHandler { if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) { response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, { index: index, - blockTransactions: mBlocksWithTransactions[index].transactions, + blockTransactions: mBlocksWithTransactions[index].transactions.map(mempoolBlocks.compressTx), }); } else { response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, { diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index ead0a84ad..71612f25f 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -65,9 +65,9 @@ export interface MempoolBlockWithTransactions extends MempoolBlock { } export interface MempoolBlockDelta { - added: TransactionClassified[]; + added: TransactionCompressed[]; removed: string[]; - changed: { txid: string, rate: number | undefined, flags?: number }[]; + changed: MempoolDeltaChange[]; } interface VinStrippedToScriptsig { @@ -196,6 +196,11 @@ export interface TransactionClassified extends TransactionStripped { flags: number; } +// [txid, fee, vsize, value, rate, flags, acceleration?] +export type TransactionCompressed = [string, number, number, number, number, number, 1?]; +// [txid, rate, flags, acceleration?] +export type MempoolDeltaChange = [string, number, number, (1|0)]; + // binary flags for transaction classification export const TransactionFlags = { // features diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts index 7fb036718..5bbf80dd9 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts @@ -99,7 +99,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang const inOldBlock = {}; const inNewBlock = {}; const added: TransactionStripped[] = []; - const changed: { txid: string, rate: number | undefined, acc: boolean | undefined }[] = []; + const changed: { txid: string, rate: number | undefined, flags: number, acc: boolean | undefined }[] = []; const removed: string[] = []; for (const tx of transactionsStripped) { inNewBlock[tx.txid] = true; @@ -117,6 +117,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang changed.push({ txid: tx.txid, rate: tx.rate, + flags: tx.flags, acc: tx.acc }); } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 35bcbe9cc..ff5977332 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -70,9 +70,15 @@ export interface MempoolBlockWithTransactions extends MempoolBlock { } export interface MempoolBlockDelta { - added: TransactionStripped[], - removed: string[], - changed?: { txid: string, rate: number | undefined, acc: boolean | undefined }[]; + added: TransactionStripped[]; + removed: string[]; + changed: { txid: string, rate: number, flags: number, acc: boolean }[]; +} + +export interface MempoolBlockDeltaCompressed { + added: TransactionCompressed[]; + removed: string[]; + changed: MempoolDeltaChange[]; } export interface MempoolInfo { @@ -97,6 +103,11 @@ export interface TransactionStripped { context?: 'projected' | 'actual'; } +// [txid, fee, vsize, value, rate, flags, acceleration?] +export type TransactionCompressed = [string, number, number, number, number, number, 1?]; +// [txid, rate, flags, acceleration?] +export type MempoolDeltaChange = [string, number, number, (1|0)]; + export interface IBackendInfo { hostname?: string; gitCommit: string; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index f87a3dc31..c65432ec1 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core'; import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs'; import { Transaction } from '../interfaces/electrs.interface'; -import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface'; +import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionCompressed, TransactionStripped } from '../interfaces/websocket.interface'; import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; import { Router, NavigationStart } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 3c72252db..11e24ef71 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -8,6 +8,7 @@ import { ApiService } from './api.service'; import { take } from 'rxjs/operators'; import { TransferState, makeStateKey } from '@angular/platform-browser'; import { CacheService } from './cache.service'; +import { uncompressDeltaChange, uncompressTx } from '../shared/common.utils'; const OFFLINE_RETRY_AFTER_MS = 2000; const OFFLINE_PING_CHECK_AFTER_MS = 30000; @@ -382,9 +383,9 @@ export class WebsocketService { if (response['projected-block-transactions']) { if (response['projected-block-transactions'].index == this.trackingMempoolBlock) { if (response['projected-block-transactions'].blockTransactions) { - this.stateService.mempoolBlockTransactions$.next(response['projected-block-transactions'].blockTransactions); + this.stateService.mempoolBlockTransactions$.next(response['projected-block-transactions'].blockTransactions.map(uncompressTx)); } else if (response['projected-block-transactions'].delta) { - this.stateService.mempoolBlockDelta$.next(response['projected-block-transactions'].delta); + this.stateService.mempoolBlockDelta$.next(uncompressDeltaChange(response['projected-block-transactions'].delta)); } } } diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts index a04fa1663..18a330fab 100644 --- a/frontend/src/app/shared/common.utils.ts +++ b/frontend/src/app/shared/common.utils.ts @@ -1,3 +1,5 @@ +import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed, TransactionStripped } from "../interfaces/websocket.interface"; + export function isMobile(): boolean { return (window.innerWidth <= 767.98); } @@ -152,4 +154,29 @@ export function seoDescriptionNetwork(network: string): string { return ' ' + network.charAt(0).toUpperCase() + network.slice(1); } return ''; +} + +export function uncompressTx(tx: TransactionCompressed): TransactionStripped { + return { + txid: tx[0], + fee: tx[1], + vsize: tx[2], + value: tx[3], + rate: tx[4], + flags: tx[5], + acc: !!tx[6], + }; +} + +export function uncompressDeltaChange(delta: MempoolBlockDeltaCompressed): MempoolBlockDelta { + return { + added: delta.added.map(uncompressTx), + removed: delta.removed, + changed: delta.changed.map(tx => ({ + txid: tx[0], + rate: tx[1], + flags: tx[2], + acc: !!tx[3], + })) + }; } \ No newline at end of file From ddee5f927c170884ca16412e19da3201898509a0 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 8 Feb 2024 17:44:32 +0000 Subject: [PATCH 06/18] Make dashboard filters persistent, add disjunctive filter mode --- .../block-filters.component.html | 11 ++++- .../block-filters.component.scss | 43 +++++++++++++++++ .../block-filters/block-filters.component.ts | 26 +++++++---- .../block-overview-graph.component.ts | 19 +++++--- .../mempool-block-overview.component.ts | 3 +- .../app/dashboard/dashboard.component.html | 6 +-- .../src/app/dashboard/dashboard.component.ts | 46 ++++++++++++++++--- frontend/src/app/services/state.service.ts | 3 +- frontend/src/app/shared/filters.utils.ts | 15 ++++++ 9 files changed, 145 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html index f60b04cdd..8c79cd438 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -1,4 +1,4 @@ -
+
Match
+
+ + +
{{ group.label }}
diff --git a/frontend/src/app/components/block-filters/block-filters.component.scss b/frontend/src/app/components/block-filters/block-filters.component.scss index 6406a1d93..4830f9540 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.scss +++ b/frontend/src/app/components/block-filters/block-filters.component.scss @@ -77,6 +77,49 @@ } } + &.any-mode { + .filter-tag { + border: solid 1px #1a9436; + &.selected { + background-color: #1a9436; + } + } + } + + .btn-group { + font-size: 0.9em; + margin-right: 0.25em; + } + + .mode-toggle { + padding: 0.2em 0.5em; + pointer-events: all; + line-height: 1.5; + background: #181b2daf; + + &:first-child { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; + } + &:last-child { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; + } + + &.blue { + border: solid 1px #105fb0; + &.active { + background: #105fb0; + } + } + &.green { + border: solid 1px #1a9436; + &.active { + background: #1a9436; + } + } + } + :host-context(.block-overview-graph:hover) &, &:hover, &:active { .menu-toggle { opacity: 0.5; diff --git a/frontend/src/app/components/block-filters/block-filters.component.ts b/frontend/src/app/components/block-filters/block-filters.component.ts index 9951984df..a16475c23 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.ts +++ b/frontend/src/app/components/block-filters/block-filters.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core'; -import { FilterGroups, TransactionFilters } from '../../shared/filters.utils'; +import { ActiveFilter, FilterGroups, FilterMode, TransactionFilters } from '../../shared/filters.utils'; import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; @@ -12,7 +12,7 @@ import { Subscription } from 'rxjs'; export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { @Input() cssWidth: number = 800; @Input() excludeFilters: string[] = []; - @Output() onFilterChanged: EventEmitter = new EventEmitter(); + @Output() onFilterChanged: EventEmitter = new EventEmitter(); filterSubscription: Subscription; @@ -21,6 +21,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { disabledFilters: { [key: string]: boolean } = {}; activeFilters: string[] = []; filterFlags: { [key: string]: boolean } = {}; + filterMode: FilterMode = 'and'; menuOpen: boolean = false; constructor( @@ -29,15 +30,16 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { ) {} ngOnInit(): void { - this.filterSubscription = this.stateService.activeGoggles$.subscribe((activeFilters: string[]) => { + this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => { + this.filterMode = active.mode; for (const key of Object.keys(this.filterFlags)) { this.filterFlags[key] = false; } - for (const key of activeFilters) { + for (const key of active.filters) { this.filterFlags[key] = !this.disabledFilters[key]; } - this.activeFilters = [...activeFilters.filter(key => !this.disabledFilters[key])]; - this.onFilterChanged.emit(this.getBooleanFlags()); + this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])]; + this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters }); }); } @@ -53,6 +55,12 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { } } + setFilterMode(mode): void { + this.filterMode = mode; + this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters }); + this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] }); + } + toggleFilter(key): void { const filter = this.filters[key]; this.filterFlags[key] = !this.filterFlags[key]; @@ -73,8 +81,8 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { this.activeFilters = this.activeFilters.filter(f => f != key); } const booleanFlags = this.getBooleanFlags(); - this.onFilterChanged.emit(booleanFlags); - this.stateService.activeGoggles$.next([...this.activeFilters]); + this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters }); + this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] }); } getBooleanFlags(): bigint | null { @@ -90,7 +98,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { @HostListener('document:click', ['$event']) onClick(event): boolean { // click away from menu - if (!event.target.closest('button')) { + if (!event.target.closest('button') && !event.target.closest('label')) { this.menuOpen = false; } return true; diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index ac1df2bf5..f250f3744 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -9,6 +9,7 @@ import { Price } from '../../services/price.service'; import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils'; +import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils'; const unmatchedOpacity = 0.2; const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity)); @@ -42,7 +43,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On @Input() showFilters: boolean = false; @Input() excludeFilters: string[] = []; @Input() filterFlags: bigint | null = null; - @Input() filterMode: 'and' | 'or' = 'and'; + @Input() filterMode: FilterMode = 'and'; @Input() blockConversion: Price; @Input() overrideColors: ((tx: TxView) => Color) | null = null; @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>(); @@ -119,10 +120,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } } - setFilterFlags(flags?: bigint | null): void { - this.activeFilterFlags = this.filterFlags || flags || null; + setFilterFlags(goggle?: ActiveFilter): void { + this.filterMode = goggle?.mode || this.filterMode; + this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags; if (this.scene) { - if (this.activeFilterFlags != null) { + if (this.activeFilterFlags != null && this.filtersAvailable) { this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags)); } else { this.scene.setColorFunction(this.overrideColors); @@ -157,7 +159,11 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On // initialize the scene without any entry transition setup(transactions: TransactionStripped[]): void { - this.filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false); + const filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false); + if (filtersAvailable !== this.filtersAvailable) { + this.setFilterFlags(); + } + this.filtersAvailable = filtersAvailable; if (this.scene) { this.scene.setup(transactions); this.readyNextFrame = true; @@ -523,8 +529,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) { + console.log('getting filter color function: ', flags, this.filterMode); return (tx: TxView) => { - if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (tx.bigintFlags & flags) > 0n)) { + if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) { return defaultColorFunction(tx); } else { return defaultColorFunction( diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts index 7fb036718..ba804912b 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts @@ -10,6 +10,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi import { Router } from '@angular/router'; import { Color } from '../block-overview-graph/sprite-types'; import TxView from '../block-overview-graph/tx-view'; +import { FilterMode } from '../../shared/filters.utils'; @Component({ selector: 'app-mempool-block-overview', @@ -22,7 +23,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang @Input() showFilters: boolean = false; @Input() overrideColors: ((tx: TxView) => Color) | null = null; @Input() filterFlags: bigint | undefined = undefined; - @Input() filterMode: 'and' | 'or' = 'and'; + @Input() filterMode: FilterMode = 'and'; @Output() txPreviewEvent = new EventEmitter(); @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent; diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index a2c46a198..6425e3b9a 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -26,7 +26,7 @@
@@ -34,8 +34,8 @@
diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index 5dfd68419..ce0db77c1 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -7,6 +7,7 @@ import { ApiService } from '../services/api.service'; import { StateService } from '../services/state.service'; import { WebsocketService } from '../services/websocket.service'; import { SeoService } from '../services/seo.service'; +import { ActiveFilter, FilterMode, toFlags } from '../shared/filters.utils'; interface MempoolBlocksData { blocks: number; @@ -55,6 +56,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { currentReserves$: Observable; fullHistory$: Observable; isLoad: boolean = true; + filterSubscription: Subscription; mempoolInfoSubscription: Subscription; currencySubscription: Subscription; currency: string; @@ -65,13 +67,15 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { private lastReservesBlockUpdate: number = 0; goggleResolution = 82; - goggleCycle = [ - { index: 0, name: 'All' }, - { index: 1, name: 'Consolidations', flag: 0b00000010_00000000_00000000_00000000_00000000n }, - { index: 2, name: 'Coinjoin', flag: 0b00000001_00000000_00000000_00000000_00000000n }, - { index: 3, name: '💩', flag: 0b00000100_00000000_00000000_00000000n | 0b00000010_00000000_00000000_00000000n | 0b00000001_00000000_00000000_00000000n }, + goggleCycle: { index: number, name: string, mode: FilterMode, filters: string[] }[] = [ + { index: 0, name: 'All', mode: 'and', filters: [] }, + { index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] }, + { index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] }, + { index: 3, name: '💩', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] }, ]; - goggleIndex = 0; // Math.floor(Math.random() * this.goggleCycle.length); + goggleFlags = 0n; + goggleMode: FilterMode = 'and'; + goggleIndex = 0; private destroy$ = new Subject(); @@ -87,6 +91,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { } ngOnDestroy(): void { + this.filterSubscription.unsubscribe(); this.mempoolInfoSubscription.unsubscribe(); this.currencySubscription.unsubscribe(); this.websocketService.stopTrackRbfSummary(); @@ -107,6 +112,30 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100) ); + this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => { + const activeFilters = active.filters.sort().join(','); + for (const goggle of this.goggleCycle) { + if (goggle.mode === active.mode) { + const goggleFilters = goggle.filters.sort().join(','); + if (goggleFilters === activeFilters) { + this.goggleIndex = goggle.index; + this.goggleFlags = toFlags(goggle.filters); + this.goggleMode = goggle.mode; + return; + } + } + } + this.goggleCycle.push({ + index: this.goggleCycle.length, + name: 'Custom', + mode: active.mode, + filters: active.filters, + }); + this.goggleIndex = this.goggleCycle.length - 1; + this.goggleFlags = toFlags(active.filters); + this.goggleMode = active.mode; + }); + this.mempoolInfoData$ = combineLatest([ this.stateService.mempoolInfo$, this.stateService.vbytesPerSecond$ @@ -375,6 +404,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { return Array.from({ length: num }, (_, i) => i + 1); } + setFilter(index): void { + const selected = this.goggleCycle[index]; + this.stateService.activeGoggles$.next(selected); + } + @HostListener('window:resize', ['$event']) onResize(): void { if (window.innerWidth >= 992) { diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index f87a3dc31..8b69546ba 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -9,6 +9,7 @@ import { filter, map, scan, shareReplay } from 'rxjs/operators'; import { StorageService } from './storage.service'; import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils'; import { ApiService } from './api.service'; +import { ActiveFilter } from '../shared/filters.utils'; export interface MarkBlockState { blockHeight?: number; @@ -150,7 +151,7 @@ export class StateService { searchFocus$: Subject = new Subject(); menuOpen$: BehaviorSubject = new BehaviorSubject(false); - activeGoggles$: BehaviorSubject = new BehaviorSubject([]); + activeGoggles$: BehaviorSubject = new BehaviorSubject({ mode: 'and', filters: [] }); constructor( @Inject(PLATFORM_ID) private platformId: any, diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts index 0b652a192..3930dc8ca 100644 --- a/frontend/src/app/shared/filters.utils.ts +++ b/frontend/src/app/shared/filters.utils.ts @@ -7,6 +7,13 @@ export interface Filter { important?: boolean, } +export type FilterMode = 'and' | 'or'; + +export interface ActiveFilter { + mode: FilterMode, + filters: string[], +} + // binary flags for transaction classification export const TransactionFlags = { // features @@ -43,6 +50,14 @@ export const TransactionFlags = { sighash_acp: 0b00010000_00000000_00000000_00000000_00000000_00000000n, }; +export function toFlags(filters: string[]): bigint { + let flag = 0n; + for (const filter of filters) { + flag |= TransactionFlags[filter]; + } + return flag; +} + export const TransactionFilters: { [key: string]: Filter } = { /* features */ rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true }, From 1e56ac094cd1edd2dd9b328cd6fa1886038f60b0 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 9 Feb 2024 21:29:22 +0800 Subject: [PATCH 07/18] Smaller mobile toggle buttons --- .../components/block-filters/block-filters.component.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/app/components/block-filters/block-filters.component.scss b/frontend/src/app/components/block-filters/block-filters.component.scss index 4830f9540..1009efd72 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.scss +++ b/frontend/src/app/components/block-filters/block-filters.component.scss @@ -175,6 +175,11 @@ .filter-tag { font-size: 0.7em; } + .mode-toggle { + font-size: 0.7em; + margin-bottom: 5px; + margin-top: 2px; + } } &.tiny { From e6266ecedc221d9b686284ada94c0ee9b1470f37 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Fri, 9 Feb 2024 15:49:28 +0100 Subject: [PATCH 08/18] [auth] properly refresh user when `auth` object changes --- .../src/app/services/services-api.service.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index 758829852..17ffe33f1 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -1,3 +1,4 @@ +import { Router, NavigationStart } from '@angular/router'; import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { StateService } from './state.service'; @@ -30,16 +31,20 @@ const SERVICES_API_PREFIX = `/api/v1/services`; providedIn: 'root' }) export class ServicesApiServices { - private apiBaseUrl: string; // base URL is protocol, hostname, and port - private apiBasePath: string; // network path is /testnet, etc. or '' for mainnet + apiBaseUrl: string; // base URL is protocol, hostname, and port + apiBasePath: string; // network path is /testnet, etc. or '' for mainnet userSubject$ = new ReplaySubject(1); + currentAuth = null; constructor( private httpClient: HttpClient, private stateService: StateService, - private storageService: StorageService + private storageService: StorageService, + private router: Router, ) { + this.currentAuth = localStorage.getItem('auth'); + this.apiBaseUrl = ''; // use relative URL by default if (!stateService.isBrowser) { // except when inside AU SSR process this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT; @@ -59,6 +64,15 @@ export class ServicesApiServices { } this.getUserInfo$().subscribe(); + console.log('refresh user'); + this.router.events.subscribe((event) => { + if (event instanceof NavigationStart) { + if (this.currentAuth !== localStorage.getItem('auth')) { + console.log('refresh user'); + this.getUserInfo$().subscribe(); + } + } + }); } /** From bb43599493a8ec5ffb585f8d6145a8dcecea5fba Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Fri, 9 Feb 2024 15:53:20 +0100 Subject: [PATCH 09/18] [auth] remove debug log --- frontend/src/app/services/services-api.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index 17ffe33f1..3bb459b92 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -64,11 +64,9 @@ export class ServicesApiServices { } this.getUserInfo$().subscribe(); - console.log('refresh user'); this.router.events.subscribe((event) => { if (event instanceof NavigationStart) { if (this.currentAuth !== localStorage.getItem('auth')) { - console.log('refresh user'); this.getUserInfo$().subscribe(); } } From e23de97e0f699fc08fdfa13210a688d8fd356759 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 9 Feb 2024 17:42:20 +0000 Subject: [PATCH 10/18] =?UTF-8?q?=F0=9F=92=A9=20=3D>=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/dashboard/dashboard.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index ce0db77c1..1714d3d01 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -71,7 +71,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { { index: 0, name: 'All', mode: 'and', filters: [] }, { index: 1, name: 'Consolidation', mode: 'and', filters: ['consolidation'] }, { index: 2, name: 'Coinjoin', mode: 'and', filters: ['coinjoin'] }, - { index: 3, name: '💩', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] }, + { index: 3, name: 'Data', mode: 'or', filters: ['inscription', 'fake_pubkey', 'op_return'] }, ]; goggleFlags = 0n; goggleMode: FilterMode = 'and'; From ade256efc75ac6ce45d1bd242e8deae03a772293 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 9 Feb 2024 18:01:25 +0000 Subject: [PATCH 11/18] Handle missing webgl on dashboards --- .../block-overview-graph.component.html | 5 ++++- .../block-overview-graph.component.scss | 13 +++++++++++++ .../block-overview-graph.component.ts | 17 +++++++++++------ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html index 9d27d8d90..34d192678 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html @@ -13,6 +13,9 @@ [auditEnabled]="auditHighlighting" [blockConversion]="blockConversion" > - + +
+ Your browser does not support this feature. +
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss index d30dd3305..92964d948 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss @@ -7,6 +7,19 @@ justify-content: center; align-items: center; grid-column: 1/-1; + + .placeholder { + display: flex; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + height: 100%; + width: 100%; + align-items: center; + justify-content: center; + } } .grid-align { diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index f250f3744..95305d72f 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -10,6 +10,7 @@ import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils'; import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils'; +import { detectWebGL } from '../../shared/graphs.utils'; const unmatchedOpacity = 0.2; const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity)); @@ -77,11 +78,14 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On filtersAvailable: boolean = true; activeFilterFlags: bigint | null = null; + webGlEnabled = true; + constructor( readonly ngZone: NgZone, readonly elRef: ElementRef, private stateService: StateService, ) { + this.webGlEnabled = detectWebGL(); this.vertexArray = new FastVertexArray(512, TxSprite.dataSize); this.searchSubscription = this.stateService.searchText$.subscribe((text) => { this.searchText = text; @@ -506,11 +510,13 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) { - const x = cssX * window.devicePixelRatio; - const y = cssY * window.devicePixelRatio; - const selected = this.scene.getTxAt({ x, y }); - if (selected && selected.txid) { - this.txClickEvent.emit({ tx: selected, keyModifier }); + if (this.scene) { + const x = cssX * window.devicePixelRatio; + const y = cssY * window.devicePixelRatio; + const selected = this.scene.getTxAt({ x, y }); + if (selected && selected.txid) { + this.txClickEvent.emit({ tx: selected, keyModifier }); + } } } @@ -529,7 +535,6 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) { - console.log('getting filter color function: ', flags, this.filterMode); return (tx: TxView) => { if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) { return defaultColorFunction(tx); From a2b01587b15b0d84fa9e3799c80d712d95c8d172 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 10 Feb 2024 10:31:56 +0100 Subject: [PATCH 12/18] [auth] small refactor --- frontend/src/app/services/services-api.service.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index 3bb459b92..f11b3460c 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -4,7 +4,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { StateService } from './state.service'; import { StorageService } from './storage.service'; import { MenuGroup } from '../interfaces/services.interface'; -import { Observable, of, ReplaySubject, tap, catchError, share } from 'rxjs'; +import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap } from 'rxjs'; import { IBackendInfo } from '../interfaces/websocket.interface'; import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface'; @@ -64,13 +64,10 @@ export class ServicesApiServices { } this.getUserInfo$().subscribe(); - this.router.events.subscribe((event) => { - if (event instanceof NavigationStart) { - if (this.currentAuth !== localStorage.getItem('auth')) { - this.getUserInfo$().subscribe(); - } - } - }); + this.router.events.pipe( + filter((event) => event instanceof NavigationStart && this.currentAuth !== localStorage.getItem('auth')), + switchMap(() => this.getUserInfo$()), + ).subscribe(); } /** From be183ada0a950ebf129bd5db1dcfdda8d82c297a Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 10 Feb 2024 10:56:19 +0100 Subject: [PATCH 13/18] [doc] fix acceleration history endpoint detail --- frontend/src/app/docs/api-docs/api-docs-data.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index a0d292925..d2cfc0926 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -9955,8 +9955,8 @@ export const restApiDocsData = [ headers: "api_key: stacksats", response: `{ "balance": 99900000, - "hold": 0, - "feesPaid": 200000 + "hold": 101829, + "feesPaid": 133721 }`, }, } @@ -10044,15 +10044,15 @@ export const restApiDocsData = [ fragment: "history", title: "GET Acceleration History", description: { - default: "

Return the history of previous acceleration requests.

Pass one of the following for :status: all, requested, accelerating, mined, completed, failed.

" + default: "

Return the history of previous acceleration requests.

Pass one of the following for :status: all, requested, accelerating, mined, completed, failed.
Pass true in :details to get a detailed history of the acceleration request.

" }, - urlString: "/v1/services/accelerator/history?status=:status", + urlString: "/v1/services/accelerator/history?status=:status&details=:details", showConditions: [""], showJsExamples: showJsExamplesDefaultFalse, codeExample: { default: { codeTemplate: { - curl: `/api/v1/services/accelerator/history?status=all`, + curl: `/api/v1/services/accelerator/history?status=all&details=true`, commonJS: ``, esModule: `` }, From 740f9af9af4574cfe03e6b092ef64f09dfead3ea Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 10 Feb 2024 11:40:19 +0100 Subject: [PATCH 14/18] [doc] additional rest api doc for accelerator --- .../src/app/docs/api-docs/api-docs-data.ts | 123 +++++++++++++++++- 1 file changed, 117 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index d2cfc0926..621f14595 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -9885,7 +9885,7 @@ export const restApiDocsData = [ type: "endpoint", category: "accelerator", httpRequestMethod: "GET", - fragment: "get-deposit-history", + fragment: "accelerator-deposit-history", title: "GET Deposit History", description: { default: "

Returns a list of deposits made as prepayment for the accelerator service.

" @@ -9933,7 +9933,7 @@ export const restApiDocsData = [ type: "endpoint", category: "accelerator", httpRequestMethod: "GET", - fragment: "balance", + fragment: "accelerator-balance", title: "GET Available Balance", description: { default: "

Returns your currently available balance, currently locked funds, and total fees paid so far.

" @@ -9967,7 +9967,7 @@ export const restApiDocsData = [ type: "endpoint", category: "accelerator", httpRequestMethod: "POST", - fragment: "estimate", + fragment: "accelerator-estimate", title: "POST Calculate Estimated Costs", description: { default: "

Returns estimated costs to accelerate a transaction.

" @@ -10011,7 +10011,7 @@ export const restApiDocsData = [ type: "endpoint", category: "accelerator", httpRequestMethod: "POST", - fragment: "accelerate", + fragment: "accelerator-accelerate", title: "POST Accelerate A Transaction", description: { default: "

Send a request to accelerate a transaction.

" @@ -10039,9 +10039,9 @@ export const restApiDocsData = [ { options: { officialOnly: true }, type: "endpoint", - category: "history", + category: "accelerator", httpRequestMethod: "GET", - fragment: "history", + fragment: "accelerator-history", title: "GET Acceleration History", description: { default: "

Return the history of previous acceleration requests.

Pass one of the following for :status: all, requested, accelerating, mined, completed, failed.
Pass true in :details to get a detailed history of the acceleration request.

" @@ -10152,6 +10152,117 @@ export const restApiDocsData = [ } ] } +]`, + }, + } + } + }, + { + options: { officialOnly: true }, + type: "endpoint", + category: "accelerator", + httpRequestMethod: "GET", + fragment: "accelerator-pending", + title: "GET Pending Acceleration", + description: { + default: "

Return the list of currently accelerated transactions.

" + }, + 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 Acceleration History", + description: { + default: `

Return a list of accelerated transactions. + Filters can be applied such as

` + }, + 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" + } + ] + } ]`, }, } From a1bd84ea6d5d312f4be4b12af4b8e40988bc814d Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 10 Feb 2024 11:41:30 +0100 Subject: [PATCH 15/18] [doc] separate private/public acceleration history endpoint title --- frontend/src/app/docs/api-docs/api-docs-data.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 621f14595..98da17d35 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -10042,7 +10042,7 @@ export const restApiDocsData = [ category: "accelerator", httpRequestMethod: "GET", fragment: "accelerator-history", - title: "GET Acceleration History", + title: "GET Private Acceleration History", description: { default: "

Return the history of previous acceleration requests.

Pass one of the following for :status: all, requested, accelerating, mined, completed, failed.
Pass true in :details to get a detailed history of the acceleration request.

" }, @@ -10214,7 +10214,7 @@ export const restApiDocsData = [ category: "accelerator", httpRequestMethod: "GET", fragment: "accelerator-public-history", - title: "GET Acceleration History", + title: "GET Public Acceleration History", description: { default: `

Return a list of accelerated transactions. Filters can be applied such as