diff --git a/backend/package-lock.json b/backend/package-lock.json
index 2f1e30d27..0576e27df 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -18,12 +18,12 @@
"crypto-js": "~4.2.0",
"express": "~4.19.2",
"maxmind": "~4.3.11",
- "mysql2": "~3.9.4",
+ "mysql2": "~3.9.7",
"redis": "^4.6.6",
"rust-gbt": "file:./rust-gbt",
"socks-proxy-agent": "~7.0.0",
"typescript": "~4.9.3",
- "ws": "~8.16.0"
+ "ws": "~8.17.0"
},
"devDependencies": {
"@babel/code-frame": "^7.18.6",
@@ -6197,9 +6197,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mysql2": {
- "version": "3.9.4",
- "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.4.tgz",
- "integrity": "sha512-OEESQuwxMza803knC1YSt7NMuc1BrK9j7gZhCSs2WAyxr1vfiI7QLaLOKTh5c9SWGz98qVyQUbK8/WckevNQhg==",
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz",
+ "integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==",
"dependencies": {
"denque": "^2.1.0",
"generate-function": "^2.3.1",
@@ -7690,9 +7690,9 @@
}
},
"node_modules/ws": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
- "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
+ "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"engines": {
"node": ">=10.0.0"
},
@@ -12382,9 +12382,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"mysql2": {
- "version": "3.9.4",
- "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.4.tgz",
- "integrity": "sha512-OEESQuwxMza803knC1YSt7NMuc1BrK9j7gZhCSs2WAyxr1vfiI7QLaLOKTh5c9SWGz98qVyQUbK8/WckevNQhg==",
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz",
+ "integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==",
"requires": {
"denque": "^2.1.0",
"generate-function": "^2.3.1",
@@ -13424,9 +13424,9 @@
}
},
"ws": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
- "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
+ "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"requires": {}
},
"y18n": {
diff --git a/backend/package.json b/backend/package.json
index 53eb6fc60..a330fa709 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -47,12 +47,12 @@
"crypto-js": "~4.2.0",
"express": "~4.19.2",
"maxmind": "~4.3.11",
- "mysql2": "~3.9.4",
+ "mysql2": "~3.9.7",
"rust-gbt": "file:./rust-gbt",
"redis": "^4.6.6",
"socks-proxy-agent": "~7.0.0",
"typescript": "~4.9.3",
- "ws": "~8.16.0"
+ "ws": "~8.17.0"
},
"devDependencies": {
"@babel/code-frame": "^7.18.6",
diff --git a/backend/src/api/explorer/channels.routes.ts b/backend/src/api/explorer/channels.routes.ts
index f28ab2a9d..391bf628e 100644
--- a/backend/src/api/explorer/channels.routes.ts
+++ b/backend/src/api/explorer/channels.routes.ts
@@ -54,9 +54,11 @@ class ChannelsRoutes {
if (index < -1) {
res.status(400).send('Invalid index');
+ return;
}
if (['open', 'active', 'closed'].includes(status) === false) {
res.status(400).send('Invalid status');
+ return;
}
const channels = await channelsApi.$getChannelsForNode(req.query.public_key, index, 10, status);
diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts
index d4ff7efe3..fdda3df88 100644
--- a/backend/src/api/websocket-handler.ts
+++ b/backend/src/api/websocket-handler.ts
@@ -3,6 +3,7 @@ import * as WebSocket from 'ws';
import {
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo,
+ MempoolBlockDelta, MempoolDelta, MempoolDeltaTxids
} from '../mempool.interfaces';
import blocks from './blocks';
import memPool from './mempool';
@@ -364,6 +365,18 @@ class WebsocketHandler {
client['track-donation'] = parsedMessage['track-donation'];
}
+ if (parsedMessage['track-mempool-txids'] === true) {
+ client['track-mempool-txids'] = true;
+ } else if (parsedMessage['track-mempool-txids'] === false) {
+ delete client['track-mempool-txids'];
+ }
+
+ if (parsedMessage['track-mempool'] === true) {
+ client['track-mempool'] = true;
+ } else if (parsedMessage['track-mempool'] === false) {
+ delete client['track-mempool'];
+ }
+
if (Object.keys(response).length) {
client.send(this.serializeResponse(response));
}
@@ -545,6 +558,33 @@ class WebsocketHandler {
const latestTransactions = memPool.getLatestTransactions();
+ if (memPool.isInSync()) {
+ this.mempoolSequence++;
+ }
+
+ const replacedTransactions: { replaced: string, by: TransactionExtended }[] = [];
+ for (const tx of newTransactions) {
+ if (rbfTransactions[tx.txid]) {
+ for (const replaced of rbfTransactions[tx.txid]) {
+ replacedTransactions.push({ replaced: replaced.txid, by: tx });
+ }
+ }
+ }
+ const mempoolDeltaTxids: MempoolDeltaTxids = {
+ sequence: this.mempoolSequence,
+ added: newTransactions.map(tx => tx.txid),
+ removed: deletedTransactions.map(tx => tx.txid),
+ mined: [],
+ replaced: replacedTransactions.map(replacement => ({ replaced: replacement.replaced, by: replacement.by.txid })),
+ };
+ const mempoolDelta: MempoolDelta = {
+ sequence: this.mempoolSequence,
+ added: newTransactions,
+ removed: deletedTransactions.map(tx => tx.txid),
+ mined: [],
+ replaced: replacedTransactions,
+ };
+
// update init data
const socketDataFields = {
'mempoolInfo': mempoolInfo,
@@ -604,10 +644,6 @@ class WebsocketHandler {
const addressCache = this.makeAddressCache(newTransactions);
const removedAddressCache = this.makeAddressCache(deletedTransactions);
- if (memPool.isInSync()) {
- this.mempoolSequence++;
- }
-
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach(async (client) => {
@@ -847,6 +883,14 @@ class WebsocketHandler {
response['rbfLatestSummary'] = getCachedResponse('rbfLatestSummary', rbfSummary);
}
+ if (client['track-mempool-txids']) {
+ response['mempool-txids'] = getCachedResponse('mempool-txids', mempoolDeltaTxids);
+ }
+
+ if (client['track-mempool']) {
+ response['mempool-transactions'] = getCachedResponse('mempool-transactions', mempoolDelta);
+ }
+
if (Object.keys(response).length) {
client.send(this.serializeResponse(response));
}
@@ -992,6 +1036,31 @@ class WebsocketHandler {
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
+ if (memPool.isInSync()) {
+ this.mempoolSequence++;
+ }
+
+ const replacedTransactions: { replaced: string, by: TransactionExtended }[] = [];
+ for (const txid of Object.keys(rbfTransactions)) {
+ for (const replaced of rbfTransactions[txid].replaced) {
+ replacedTransactions.push({ replaced: replaced.txid, by: rbfTransactions[txid].replacedBy });
+ }
+ }
+ const mempoolDeltaTxids: MempoolDeltaTxids = {
+ sequence: this.mempoolSequence,
+ added: [],
+ removed: [],
+ mined: transactions.map(tx => tx.txid),
+ replaced: replacedTransactions.map(replacement => ({ replaced: replacement.replaced, by: replacement.by.txid })),
+ };
+ const mempoolDelta: MempoolDelta = {
+ sequence: this.mempoolSequence,
+ added: [],
+ removed: [],
+ mined: transactions.map(tx => tx.txid),
+ replaced: replacedTransactions,
+ };
+
const responseCache = { ...this.socketData };
function getCachedResponse(key, data): string {
if (!responseCache[key]) {
@@ -1000,10 +1069,6 @@ class WebsocketHandler {
return responseCache[key];
}
- if (memPool.isInSync()) {
- this.mempoolSequence++;
- }
-
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
@@ -1185,6 +1250,14 @@ class WebsocketHandler {
}
}
+ if (client['track-mempool-txids']) {
+ response['mempool-txids'] = getCachedResponse('mempool-txids', mempoolDeltaTxids);
+ }
+
+ if (client['track-mempool']) {
+ response['mempool-transactions'] = getCachedResponse('mempool-transactions', mempoolDelta);
+ }
+
if (Object.keys(response).length) {
client.send(this.serializeResponse(response));
}
diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts
index 0b4b20e02..0fcddc45a 100644
--- a/backend/src/mempool.interfaces.ts
+++ b/backend/src/mempool.interfaces.ts
@@ -71,6 +71,22 @@ export interface MempoolBlockDelta {
changed: MempoolDeltaChange[];
}
+export interface MempoolDeltaTxids {
+ sequence: number,
+ added: string[];
+ removed: string[];
+ mined: string[];
+ replaced: { replaced: string, by: string }[];
+}
+
+export interface MempoolDelta {
+ sequence: number,
+ added: MempoolTransactionExtended[];
+ removed: string[];
+ mined: string[];
+ replaced: { replaced: string, by: TransactionExtended }[];
+}
+
interface VinStrippedToScriptsig {
scriptsig: string;
}
diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile
index 3a63107bf..bee617595 100644
--- a/docker/frontend/Dockerfile
+++ b/docker/frontend/Dockerfile
@@ -13,7 +13,7 @@ RUN npm install --omit=dev --omit=optional
RUN npm run build
-FROM nginx:1.25.4-alpine
+FROM nginx:1.26.0-alpine
WORKDIR /patch
diff --git a/frontend/.gitignore b/frontend/.gitignore
index d2a765dda..c10a00946 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -63,6 +63,7 @@ src/resources/pools.json
src/resources/mining-pools/*
src/resources/**/*.mp4
src/resources/**/*.vtt
+src/resources/customize.js
# environment config
mempool-frontend-config.json
diff --git a/frontend/angular.json b/frontend/angular.json
index f55c09934..46cc3f667 100644
--- a/frontend/angular.json
+++ b/frontend/angular.json
@@ -166,6 +166,7 @@
"src/resources",
"src/robots.txt",
"src/config.js",
+ "src/customize.js",
"src/config.template.js"
],
"styles": [
diff --git a/frontend/custom-sv-config.json b/frontend/custom-sv-config.json
new file mode 100644
index 000000000..f64f41be8
--- /dev/null
+++ b/frontend/custom-sv-config.json
@@ -0,0 +1,44 @@
+{
+ "theme": "contrast",
+ "enterprise": "onbtc",
+ "branding": {
+ "name": "onbtc",
+ "title": "Oficina Nacional del Bitcoin",
+ "site_id": 19,
+ "header_img": "/resources/onbtc.svg",
+ "img": "/resources/elsalvador.svg",
+ "rounded_corner": true
+ },
+ "dashboard": {
+ "widgets": [
+ {
+ "component": "fees"
+ },
+ {
+ "component": "balance",
+ "props": {
+ "address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
+ }
+ },
+ {
+ "component": "goggles"
+ },
+ {
+ "component": "address",
+ "props": {
+ "address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo",
+ "period": "1m"
+ }
+ },
+ {
+ "component": "blocks"
+ },
+ {
+ "component": "addressTransactions",
+ "props": {
+ "address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/frontend/generate-config.js b/frontend/generate-config.js
index c7a81a482..8f911dfe6 100644
--- a/frontend/generate-config.js
+++ b/frontend/generate-config.js
@@ -4,6 +4,7 @@ const { spawnSync } = require('child_process');
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
const GENERATED_CONFIG_FILE_NAME = 'src/resources/config.js';
const GENERATED_TEMPLATE_CONFIG_FILE_NAME = 'src/resources/config.template.js';
+const GENERATED_CUSTOMIZATION_FILE_NAME = 'src/resources/customize.js';
let settings = [];
let configContent = {};
@@ -109,6 +110,23 @@ writeConfigTemplate(GENERATED_TEMPLATE_CONFIG_FILE_NAME, newConfigTemplate);
const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME);
+let customConfigJs = '';
+if (configContent && configContent.CUSTOMIZATION) {
+ const customConfig = readConfig(configContent.CUSTOMIZATION);
+ if (customConfig) {
+ console.log(`Customizing frontend using ${configContent.CUSTOMIZATION}`);
+ customConfigJs = `(function (window) {
+ window.__env = window.__env || {};
+ window.__env.customize = ${customConfig};
+ }((typeof global !== 'undefined') ? global : this));
+ `;
+ } else {
+ throw new Error('Failed to load customization file');
+ }
+}
+
+writeConfig(GENERATED_CUSTOMIZATION_FILE_NAME, customConfigJs);
+
if (currentConfig && currentConfig === newConfig) {
console.log(`No configuration updates, skipping ${GENERATED_CONFIG_FILE_NAME} file update`);
return;
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html
index e9f6badbd..44b027ae2 100644
--- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html
+++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html
@@ -23,7 +23,7 @@
Accelerate
Confirmation expected within ~30 minutes {{ error }}
@if (!calculating) {
-
+
+
+
+TXID
+ Amount
+ {{ currency }}
+ Date
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ BTC Holdings
+ Change (7d)
+ Change (30d)
+ BTC Holdings
+ Change (7d)
+ Change (30d)
+
+
+ {{ mempoolInfoData.value.memPoolInfo.size | number }} TXs +
+TXID | +Previous fee | +New fee | +Status | + + +
---|---|---|---|
+
+ |
+ + Mined + Full RBF + RBF + | +
Height | +Mined | +TXs | +Size | + + +
---|---|---|---|
{{ block.height }} | +{{ block.tx_count | number }} | +
+
+
+
+
+ |
+
TXID | +Amount | +{{ currency }} | +Fee | + + +
---|---|---|---|
+
+ |
+
Explore
- + diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index d018dd1e8..38caa13db 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -65,6 +65,8 @@ import { FeesBoxComponent } from '../components/fees-box/fees-box.component'; import { DifficultyComponent } from '../components/difficulty/difficulty.component'; import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component'; import { DifficultyMiningComponent } from '../components/difficulty-mining/difficulty-mining.component'; +import { BalanceWidgetComponent } from '../components/balance-widget/balance-widget.component'; +import { AddressTransactionsWidgetComponent } from '../components/address-transactions-widget/address-transactions-widget.component'; import { RbfTimelineComponent } from '../components/rbf-timeline/rbf-timeline.component'; import { RbfTimelineTooltipComponent } from '../components/rbf-timeline/rbf-timeline-tooltip.component'; import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component'; @@ -174,6 +176,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir DifficultyComponent, DifficultyMiningComponent, DifficultyTooltipComponent, + BalanceWidgetComponent, + AddressTransactionsWidgetComponent, RbfTimelineComponent, RbfTimelineTooltipComponent, PushTransactionComponent, @@ -311,6 +315,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir DifficultyComponent, DifficultyMiningComponent, DifficultyTooltipComponent, + BalanceWidgetComponent, + AddressTransactionsWidgetComponent, RbfTimelineComponent, RbfTimelineTooltipComponent, PushTransactionComponent, diff --git a/frontend/src/index.mempool.html b/frontend/src/index.mempool.html index 838af21d0..ed5f7e0b4 100644 --- a/frontend/src/index.mempool.html +++ b/frontend/src/index.mempool.html @@ -5,6 +5,7 @@