From b9f88303090b791c11b698d315a0c6595898f7f6 Mon Sep 17 00:00:00 2001 From: natsoni Date: Thu, 28 Aug 2025 11:58:10 +0200 Subject: [PATCH 001/369] Bump bootstrap to 5.3.6 --- frontend/package-lock.json | 47 ++++++++------------------------------ frontend/package.json | 2 +- 2 files changed, 10 insertions(+), 39 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0c26b48d2..f85059fed 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "@mempool/mempool.js": "2.3.0", "@ng-bootstrap/ng-bootstrap": "^16.0.0", "@types/qrcode": "~1.5.0", - "bootstrap": "~4.6.2", + "bootstrap": "~5.3.6", "browserify": "^17.0.0", "clipboard": "^2.0.11", "domino": "^2.1.6", @@ -6165,9 +6165,9 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/bootstrap": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", - "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", "funding": [ { "type": "github", @@ -6178,9 +6178,9 @@ "url": "https://opencollective.com/bootstrap" } ], + "license": "MIT", "peerDependencies": { - "jquery": "1.9.1 - 3", - "popper.js": "^1.16.1" + "@popperjs/core": "^2.11.8" } }, "node_modules/brace-expansion": { @@ -11629,12 +11629,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", - "peer": true - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14257,17 +14251,6 @@ "node": ">=10.13.0" } }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/portscanner": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", @@ -22550,9 +22533,9 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "bootstrap": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", - "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", "requires": {} }, "brace-expansion": { @@ -26654,12 +26637,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", - "peer": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -28639,12 +28616,6 @@ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "peer": true - }, "portscanner": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 56a552195..307546d5e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -80,7 +80,7 @@ "@mempool/mempool.js": "2.3.0", "@ng-bootstrap/ng-bootstrap": "^16.0.0", "@types/qrcode": "~1.5.0", - "bootstrap": "~4.6.2", + "bootstrap": "~5.3.6", "browserify": "^17.0.0", "clipboard": "^2.0.11", "domino": "^2.1.6", From b37eb751985e85d31a69652a1a5f2b475349c830 Mon Sep 17 00:00:00 2001 From: natsoni Date: Thu, 28 Aug 2025 11:34:21 +0200 Subject: [PATCH 002/369] bootstrap 5: left is start, right is end --- .../accelerate-checkout.component.html | 52 +++++------ .../acceleration-fees-graph.component.html | 2 +- .../accelerations-list.component.html | 74 ++++++++-------- .../accelerator-dashboard.component.html | 4 +- .../active-acceleration-box.component.html | 4 +- .../address-group.component.html | 2 +- .../components/address/address.component.html | 10 +-- .../block-fee-rates-graph.component.html | 2 +- .../block-fees-graph.component.html | 2 +- .../block-fees-subsidy-graph.component.html | 2 +- .../block-filters.component.html | 2 +- .../block-health-graph.component.html | 2 +- .../block-overview-graph.component.html | 4 +- .../block-rewards-graph.component.html | 2 +- .../block-sizes-weights-graph.component.html | 2 +- .../block/block-transactions.component.html | 6 +- .../app/components/block/block.component.html | 6 +- .../blocks-list/blocks-list.component.html | 42 ++++----- .../clipboard/clipboard.component.ts | 2 +- .../custom-dashboard.component.html | 4 +- ...ifficulty-adjustments-table.component.html | 18 ++-- .../components/faucet/faucet.component.html | 26 +++--- .../github-login.component.html | 2 +- .../hashrate-chart.component.html | 2 +- .../hashrate-chart-pools.component.html | 2 +- .../liquid-master-page.component.html | 12 +-- .../federation-addresses-list.component.html | 22 ++--- .../federation-utxos-list.component.html | 56 ++++++------ .../recent-pegs-list.component.html | 44 +++++----- .../loading-indicator.component.html | 2 +- .../master-page-preview.component.html | 14 +-- .../master-page/master-page.component.html | 16 ++-- .../mempool-block.component.html | 2 +- .../app/components/menu/menu.component.html | 6 +- .../mining-dashboard.component.html | 4 +- .../ord-data/ord-data.component.html | 4 +- .../pool-ranking/pool-ranking.component.html | 10 +-- .../app/components/pool/pool.component.html | 86 +++++++++---------- .../privacy-policy.component.html | 2 +- .../push-transaction.component.html | 8 +- .../rbf-list/rbf-list.component.html | 6 +- .../search-form/search-form.component.html | 2 +- .../simpleproof-cubo-widget.component.html | 36 ++++---- .../simpleproof-widget.component.html | 32 +++---- .../statistics/statistics.component.html | 4 +- .../stratum-list/stratum-list.component.html | 2 +- .../terms-of-service.component.html | 2 +- .../test-transactions.component.html | 4 +- .../components/tracker/tracker.component.html | 12 +-- .../trademark-policy.component.html | 2 +- .../transaction/cpfp-info.component.html | 2 +- .../transaction-details.component.html | 10 +-- .../transaction-preview.component.html | 6 +- .../transaction-raw.component.html | 8 +- .../transaction/transaction.component.html | 16 ++-- .../transactions-list.component.html | 14 +-- .../treasuries/treasuries.component.html | 2 +- .../twitter-login.component.html | 2 +- .../twitter-widget.component.html | 2 +- .../components/wallet/wallet.component.html | 4 +- .../app/dashboard/dashboard.component.html | 6 +- .../app/docs/api-docs/api-docs.component.html | 2 +- .../channels-list.component.html | 38 ++++---- .../app/lightning/group/group.component.html | 14 +-- .../justice-list/justice-list.component.html | 20 ++--- .../lightning-dashboard.component.html | 4 +- .../app/lightning/node/node.component.html | 8 +- .../nodes-list/nodes-list.component.html | 18 ++-- .../nodes-networks-chart.component.html | 2 +- .../nodes-per-country-chart.component.html | 22 ++--- .../nodes-per-country.component.html | 40 ++++----- .../nodes-per-isp-chart.component.html | 22 ++--- .../nodes-per-isp.component.html | 40 ++++----- .../oldest-nodes/oldest-nodes.component.html | 42 ++++----- .../top-nodes-per-capacity.component.html | 30 +++---- .../top-nodes-per-channels.component.html | 30 +++---- .../lightning-statistics-chart.component.html | 2 +- .../global-footer.component.html | 4 +- frontend/src/styles.scss | 22 ++--- 79 files changed, 547 insertions(+), 551 deletions(-) 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 4e40fd639..29e723054 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -129,7 +129,7 @@ sats - + } @@ -151,7 +151,7 @@ sats - + } @@ -169,7 +169,7 @@ sats - + @@ -181,7 +181,7 @@ sats - + @@ -190,16 +190,16 @@ - Estimated acceleration cost ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB + Estimated acceleration cost ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB - + {{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }} sats - + @@ -209,19 +209,19 @@ @if (hasAccessToBalanceMode) { - Maximum acceleration cost + Maximum acceleration cost } @else { - Acceleration cost + Acceleration cost } - + {{ cost | number }} sats - + @@ -237,7 +237,7 @@ sats - + @@ -326,7 +326,7 @@
- +
@@ -362,7 +362,7 @@
- +
@@ -428,9 +428,9 @@ @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { }
@if (['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) { - + } @else { - + } {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}
@@ -499,11 +499,11 @@ } @else if (step === 'googlepay') {
} @else if (step === 'cardonfile') { -
+
@if (['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) { - + } @else { - + } {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}
@@ -511,13 +511,13 @@ @if (loadingCashapp || loadingApplePay || loadingGooglePay || loadingCardOnFile) {
Loading payment method... -
+
}
@if (isTokenizing > 0) {
-
+
}
@@ -544,7 +544,7 @@
We are processing your payment... -
+
@@ -617,7 +617,7 @@ - + Accelerate to ~{{ x | number : '1.0-0' }} sat/vB @@ -625,7 +625,7 @@
@if (quoteError || cantPayReason) { @@ -637,12 +637,12 @@ @if (hasAccessToBalanceMode) { } @else { } diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html index 9146c8e34..463035e60 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html @@ -4,7 +4,7 @@
Acceleration Fees -
diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html index 26cdace5d..47d653f78 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html @@ -1,56 +1,56 @@
-

Accelerations

-
+

Accelerations

+
- + - - - + + + - - - - - + + + + + - - - - - - - - - - @@ -77,22 +77,22 @@ @if (!pending) { - - - - - - @@ -100,16 +100,16 @@ } @else { - - - - @@ -118,7 +118,7 @@
TXIDTXID Fee rateBidRequestedFee rateBidRequested Bid BoostBlockPoolStatusRequestedBid BoostBlockPoolStatusRequested
+ + + {{ (acceleration.feeDelta) | number }} sats + + {{ acceleration.boost | number }} sats + ~ + {{ acceleration.blockHeight }} ~ + @if (acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]) { @@ -60,14 +60,14 @@ ~ } - Pending - Completed ⌛ - Mined ⌛ - Canceled ⌛ - Failed ⌛ + + Pending + Completed ⌛ + Mined ⌛ + Canceled ⌛ + Failed ⌛ +
+ + + + + +
+ + + +
- diff --git a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html index b2a0aad0f..97f2bce33 100644 --- a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html +++ b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html @@ -62,7 +62,7 @@
-
+
Mempool Goggles™ : Accelerations
  @@ -78,7 +78,7 @@
-
+
Total Bid Boost
@if (hasCpfp) { - + }
@@ -36,7 +36,7 @@
- +
diff --git a/frontend/src/app/components/address-group/address-group.component.html b/frontend/src/app/components/address-group/address-group.component.html index 174853600..080aaf240 100644 --- a/frontend/src/app/components/address-group/address-group.component.html +++ b/frontend/src/app/components/address-group/address-group.component.html @@ -19,6 +19,6 @@
diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index b1d96a620..22f2d62a7 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -76,7 +76,7 @@
-

Taproot Tree

+

Taproot Tree

@@ -90,7 +90,7 @@
-

Balance History

+

Balance History

@@ -111,7 +111,7 @@
-

Unspent Outputs

+

Unspent Outputs

@@ -124,7 +124,7 @@
-

+

  {{ (transactions?.length | number) || '?' }} of {{ mempoolStats.tx_count + chainStats.tx_count | number }} transaction {{ (transactions?.length | number) || '?' }} of {{ mempoolStats.tx_count + chainStats.tx_count | number }} transactions @@ -302,7 +302,7 @@ - + diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html index e9de40559..2bfbc17af 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html @@ -4,7 +4,7 @@
Block Fee Rates -
diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html index 0328e7eb1..0c2a42d53 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html @@ -4,7 +4,7 @@
Block Fees -
diff --git a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html index 341b16640..21d7f2fef 100644 --- a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html +++ b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html @@ -4,7 +4,7 @@
Block Fees Vs Subsidy -
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 d85b22f72..d1f9aa6ac 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -1,5 +1,5 @@
- +
diff --git a/frontend/src/app/components/block-health-graph/block-health-graph.component.html b/frontend/src/app/components/block-health-graph/block-health-graph.component.html index 86eb91862..b8aa34aae 100644 --- a/frontend/src/app/components/block-health-graph/block-health-graph.component.html +++ b/frontend/src/app/components/block-health-graph/block-health-graph.component.html @@ -4,7 +4,7 @@
Block Health -
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 471257230..56bafe501 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 @@ -4,8 +4,8 @@ @if (!disableSpinner) {
-
-
not available
+
+
not available
}
Block Rewards -
diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html index 521357c4b..87932780b 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html @@ -3,7 +3,7 @@
Block Sizes and Weights -
diff --git a/frontend/src/app/components/block/block-transactions.component.html b/frontend/src/app/components/block/block-transactions.component.html index c732ea41e..e92c3c060 100644 --- a/frontend/src/app/components/block/block-transactions.component.html +++ b/frontend/src/app/components/block/block-transactions.component.html @@ -1,10 +1,10 @@
-

+

{{ i }} transaction {{ i }} transactions

- +
@@ -50,4 +50,4 @@
- + diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 7591d02a9..661293640 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -314,7 +314,7 @@
-
+
- - + +
-
+
Amount is required
Minimum is {{ amount?.errors?.['min'].min | number }} tSats
Maximum is {{ amount?.errors?.['max'].max | number }} tSats
-
- Address -
+ Address
-
+
Address is required
Must be a valid testnet4 address
You cannot use this address
diff --git a/frontend/src/app/components/github-login.component/github-login.component.html b/frontend/src/app/components/github-login.component/github-login.component.html index e57019a26..13d5ee049 100644 --- a/frontend/src/app/components/github-login.component/github-login.component.html +++ b/frontend/src/app/components/github-login.component/github-login.component.html @@ -1,5 +1,5 @@
- {{ buttonString }} + {{ buttonString }} diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html index d60404fb5..f1cd96667 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html @@ -25,7 +25,7 @@
Hashrate & Difficulty -
diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html index 77a55c954..52b147ce3 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html @@ -5,7 +5,7 @@
diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.html b/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.html index 31d27400d..3b49a4172 100644 --- a/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.html +++ b/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.html @@ -5,30 +5,30 @@
- - + + - - - - @@ -37,20 +37,20 @@ - - - - @@ -58,7 +58,7 @@
AddressBalanceAddressBalance
+ +
+ +
+ +
+ +
- diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html index ba2d14adb..8a302795f 100644 --- a/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html +++ b/frontend/src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html @@ -5,47 +5,47 @@
- - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - @@ -110,7 +110,7 @@
OutputAddressAmountRelated Peg-InDateExpires inExpired sinceDustOutputAddressAmountRelated Peg-InDateExpires inExpired sinceDust
+ + +
+ + + + @@ -55,13 +55,13 @@ Change output + + {{ utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate < 0 ? -(utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate) : utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate }} blocks + @if (utxo.isDust) { ✔ } @else { @@ -74,35 +74,35 @@
+ + +
+ + + + + +
- diff --git a/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.html b/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.html index 97c1d96cd..1f110045e 100644 --- a/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.html +++ b/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.html @@ -9,16 +9,16 @@
- - - - - + + + + + - - - - - - - - - - - - - - - - @@ -116,7 +116,7 @@
TransactionDateAmountFund / Redemption TxBTC AddressTransactionDateAmountFund / Redemption TxBTC Address
+ @@ -30,17 +30,17 @@ + +
+ @@ -52,13 +52,13 @@ + + + @@ -70,7 +70,7 @@ + @@ -83,32 +83,32 @@
+ + +
+ + + + +
- diff --git a/frontend/src/app/components/loading-indicator/loading-indicator.component.html b/frontend/src/app/components/loading-indicator/loading-indicator.component.html index 49dbc2983..378321701 100644 --- a/frontend/src/app/components/loading-indicator/loading-indicator.component.html +++ b/frontend/src/app/components/loading-indicator/loading-indicator.component.html @@ -1,3 +1,3 @@
- {{ this.label }} ({{ progress }}%) + {{ this.label }} ({{ progress }}%)
\ No newline at end of file diff --git a/frontend/src/app/components/master-page-preview/master-page-preview.component.html b/frontend/src/app/components/master-page-preview/master-page-preview.component.html index 01995906f..7cef0c13c 100644 --- a/frontend/src/app/components/master-page-preview/master-page-preview.component.html +++ b/frontend/src/app/components/master-page-preview/master-page-preview.component.html @@ -6,7 +6,7 @@ } @if (enterpriseInfo?.header_img) { - + } @else { @@ -14,12 +14,12 @@
- Signet - Testnet3 - Testnet4 - Mainnet - Testnet - Mainnet + Signet + Testnet3 + Testnet4 + Mainnet + Testnet + Mainnet
diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 8d017fc7d..a09a3043f 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -18,7 +18,7 @@ @if (enterpriseInfo?.header_img) { - + } @else { diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.html b/frontend/src/app/components/mempool-block/mempool-block.component.html index cf5e641e5..fd7018886 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.html +++ b/frontend/src/app/components/mempool-block/mempool-block.component.html @@ -2,7 +2,7 @@

{{ ordinal$ | async }}

- +
diff --git a/frontend/src/app/components/menu/menu.component.html b/frontend/src/app/components/menu/menu.component.html index 848f505a1..6c0bdb61c 100644 --- a/frontend/src/app/components/menu/menu.component.html +++ b/frontend/src/app/components/menu/menu.component.html @@ -9,15 +9,15 @@ @{{ user.username }} - + OG #{{ user.ogRank }} @if (user.subscription_tag !== 'free') { - + {{ user.subscription_tag.toUpperCase() }} } @else if (user.type === 'mining_pool') { - + MINING POOL } diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html index 09479b3f1..06aef2ea1 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -27,7 +27,7 @@
-
+
@@ -39,7 +39,7 @@
-
+
diff --git a/frontend/src/app/components/ord-data/ord-data.component.html b/frontend/src/app/components/ord-data/ord-data.component.html index 8e58e66a4..949b2c71f 100644 --- a/frontend/src/app/components/ord-data/ord-data.component.html +++ b/frontend/src/app/components/ord-data/ord-data.component.html @@ -35,9 +35,9 @@
@if (contentType.key !== 'undefined') { - {{ contentType.value.count > 1 ? contentType.value.count + " " : "" }}{{ contentType.value?.tag || contentType.key }} + {{ contentType.value.count > 1 ? contentType.value.count + " " : "" }}{{ contentType.value?.tag || contentType.key }} } @else { - Unknown + Unknown } {{ contentType.value.totalSize | bytes:2:'B':undefined:true }} diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.html b/frontend/src/app/components/pool-ranking/pool-ranking.component.html index f6aa4d4b9..26bee6afe 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.html +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.html @@ -34,7 +34,7 @@
Pools Ranking -
@@ -92,7 +92,7 @@ Pool Hashrate Blocks - Avg Health Avg Block Fees Empty Blocks @@ -101,7 +101,7 @@ {{ pool.rank }} - +
{{ pool.name }} @@ -111,7 +111,7 @@ {{ pool.blockCount }} ({{ pool.share }}%) - + - + All miners {{ miningStats.lastEstimatedHashrate| number: '1.2-2' }} {{ miningStats.miningUnits.hashrateUnit }} {{ miningStats.lastEstimatedHashrate3d | number: '1.2-2' }} {{ miningStats.miningUnits.hashrateUnit }} diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index eabfc7c63..f40d6d4c4 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -6,7 +6,7 @@
+ onError="this.onerror=null; this.src = '/resources/mining-pools/default.svg'" class="me-3">

{{ poolStats.pool.name }}

@@ -63,7 +63,7 @@ Addresses
@@ -91,19 +91,19 @@ - - - + + + - - - + + @@ -117,17 +117,17 @@
RewardHashrate (24h)Avg HealthRewardHashrate (24h)Avg Health
{{ poolStats.estimatedHashrate | amountShortener : 3 : 'H/s' : false : true }}{{ poolStats.estimatedHashrate | amountShortener : 3 : 'H/s' : false : true }}{{ poolStats.avgBlockHealth }}% - Unknown + Unknown
- - - + + + - - -
Blocks (24h)1wAllBlocks (24h)1wAll
{{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + {{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['24h'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + {{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['1w'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + {{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['all'], this.locale, '1.0-0') }}%)
@@ -140,13 +140,13 @@ - - - + + + - +
Out-of-band Fees (1w)1mAllOut-of-band Fees (1w)1mAll
@@ -303,11 +303,11 @@ Height Timestamp Mined - Coinbase tag - Health - Reward - Fees - TXs + Coinbase tag + Health + Reward + Fees + TXs Size @@ -322,31 +322,31 @@ - + {{ block.extras.coinbaseRaw | hex2ascii }} - +
{{ block.extras.matchRate }}% - Unknown + Unknown - + - + - + {{ block.tx_count | number }} @@ -365,10 +365,10 @@ Height Timestamp Mined - Coinbase tag - Reward - Fees - TXs + Coinbase tag + Reward + Fees + TXs Size @@ -385,16 +385,16 @@ - + - + - + - + @@ -408,7 +408,7 @@
-
+

diff --git a/frontend/src/app/components/privacy-policy/privacy-policy.component.html b/frontend/src/app/components/privacy-policy/privacy-policy.component.html index bfd6159c4..7851c0d6e 100644 --- a/frontend/src/app/components/privacy-policy/privacy-policy.component.html +++ b/frontend/src/app/components/privacy-policy/privacy-policy.component.html @@ -9,7 +9,7 @@

-
+

USE YOUR OWN SELF-HOSTED MEMPOOL EXPLORER

diff --git a/frontend/src/app/components/push-transaction/push-transaction.component.html b/frontend/src/app/components/push-transaction/push-transaction.component.html index 8d8402fd3..c8a3070b9 100644 --- a/frontend/src/app/components/push-transaction/push-transaction.component.html +++ b/frontend/src/app/components/push-transaction/push-transaction.component.html @@ -1,17 +1,17 @@
-

Broadcast Transaction

+

Broadcast Transaction

- +

{{ error }}

{{ txId }}
@if (network === '' || network === 'testnet' || network === 'testnet4' || network === 'signet') {
-

Submit Package

+

Submit Package

@@ -24,7 +24,7 @@
- +

{{ errorPackage }}

{{ packageMessage }}

diff --git a/frontend/src/app/components/rbf-list/rbf-list.component.html b/frontend/src/app/components/rbf-list/rbf-list.component.html index a3baa8537..e6fe423a9 100644 --- a/frontend/src/app/components/rbf-list/rbf-list.component.html +++ b/frontend/src/app/components/rbf-list/rbf-list.component.html @@ -1,8 +1,8 @@
-

RBF Replacements

-
+

RBF Replacements

+
-
+

} -
\ No newline at end of file +
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 04da15d9b..800d3b54e 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -92,7 +92,7 @@
+ [cluster]="cpfpInfo.cluster" [txid]="tx.txid" [isMobile]="isMobile">
@@ -393,4 +393,4 @@ -
\ No newline at end of file +
From 1173d6bdc464c616233e9dc537577c797fbdf40e Mon Sep 17 00:00:00 2001 From: mononaut Date: Thu, 14 May 2026 07:25:51 +0000 Subject: [PATCH 226/369] fix cluster diagram preview alignment --- .../cluster-diagram/cluster-renderer.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/cluster-diagram/cluster-renderer.ts b/frontend/src/app/components/cluster-diagram/cluster-renderer.ts index 69f30d580..8ccefbd1c 100644 --- a/frontend/src/app/components/cluster-diagram/cluster-renderer.ts +++ b/frontend/src/app/components/cluster-diagram/cluster-renderer.ts @@ -121,6 +121,7 @@ const PREVIEW_DIMENSIONS: RenderDimensions = { }; const PREVIEW_VIEWPORT_HEIGHT = 48; +const PREVIEW_SAFE_INSET = 16; const OUTLINE_PAD = 6; export function renderLayout(layout: GridLayout, params: RenderParams): RenderResult { @@ -175,7 +176,7 @@ export function renderLayout(layout: GridLayout, params: RenderParams): RenderRe let cellW: number; if (params.preview) { const activeCols = activeChunkColCount(layout, params.activeChunkIndex); - const denom = Math.max(1, activeCols); + const denom = Math.max(1, activeCols + 1); cellW = Math.max(dim.minCellW, Math.min(dim.maxCellW, (params.containerWidth - dim.marginX * 2) / denom)); } else if (layout.cols > 0) { @@ -222,15 +223,21 @@ export function renderLayout(layout: GridLayout, params: RenderParams): RenderRe activeMaxY = Math.max(activeMaxY, n.rectY + n.height); } } - const pad = dim.marginX; + const pad = dim.marginX + PREVIEW_SAFE_INSET; const chunkFits = isFinite(activeMinX) && (activeMaxX - activeMinX) + 2 * pad <= vbWidth; - const vbX = chunkFits + let vbX = chunkFits ? (activeMinX + activeMaxX) / 2 - vbWidth / 2 : selected.x - vbWidth / 2; + if (!chunkFits && isFinite(activeMinX)) { + vbX = Math.max(activeMinX - pad, Math.min(activeMaxX + pad - vbWidth, vbX)); + } const chunkFitsVertically = isFinite(activeMinY) && (activeMaxY - activeMinY) + 2 * dim.marginY <= vbHeight; - const vbY = chunkFitsVertically + let vbY = chunkFitsVertically ? (activeMinY + activeMaxY) / 2 - vbHeight / 2 : selected.y - vbHeight / 2; + if (!chunkFitsVertically && isFinite(activeMinY)) { + vbY = Math.max(activeMinY - dim.marginY, Math.min(activeMaxY + dim.marginY - vbHeight, vbY)); + } return { nodes, edges, chunkOutlines, From 1469503993754e6ac07b2e3a154688a56bddf0fa Mon Sep 17 00:00:00 2001 From: mononaut Date: Thu, 14 May 2026 08:57:19 +0000 Subject: [PATCH 227/369] fix missing fragment param in cpfp link --- frontend/src/app/components/transaction/transaction.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 4510bab5a..459914f91 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -1130,6 +1130,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { relativeTo: this.route, queryParams: { cpfp: this.cpfpMode ? 'true' : null }, queryParamsHandling: 'merge', + fragment: this.formatFragment(this.fragmentParams), replaceUrl: true, }); } From d11dbc61ff677e997513dea9ed2e6665eb4652ff Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 15 May 2026 07:10:38 +0200 Subject: [PATCH 228/369] [npm] fix unsynced package-lock --- frontend/package-lock.json | 245 +++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0f0b850c0..31efabc31 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -354,6 +354,23 @@ } } }, + "node_modules/@angular-devkit/architect/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/architect/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -372,6 +389,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/architect/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/architect/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -989,6 +1021,23 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1107,6 +1156,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -1243,6 +1307,23 @@ } } }, + "node_modules/@angular-devkit/schematics/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1261,6 +1342,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/schematics/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/schematics/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -2282,6 +2378,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@angular/cli/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/cli/node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -2437,6 +2550,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular/cli/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/cli/node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -7688,6 +7816,23 @@ } } }, + "node_modules/@schematics/angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@schematics/angular/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7706,6 +7851,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@schematics/angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@schematics/angular/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19085,6 +19245,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -19095,6 +19265,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19352,6 +19529,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -19432,6 +19619,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -19508,6 +19702,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -19518,6 +19722,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -20004,6 +20215,16 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -20098,6 +20319,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -22980,6 +23208,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -22990,6 +23228,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", From 67b8c2c9b3a11952682ee82ec60d6bcf69146642 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 2 Apr 2026 09:55:58 +0000 Subject: [PATCH 229/369] shill the accelerator harder --- .../transaction-details.component.html | 5 ++ .../transaction-details.component.ts | 66 +++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html index f6decfacf..89655db13 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -46,6 +46,11 @@ + @if (showAcceleratorSavingsMsg) { + + Could have saved using Mempool Accelerator Pro™ + + } @if (!isLoadingTx && !tx?.status?.confirmed && isAcceleration) { } @else { diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts index 39272b684..20af39a58 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts @@ -1,11 +1,14 @@ -import { Component, OnInit, Input, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core'; +import { Component, OnChanges, SimpleChanges, Input, ChangeDetectionStrategy, ChangeDetectorRef, Output, EventEmitter } from '@angular/core'; import { Transaction } from '@interfaces/electrs.interface'; -import { Acceleration, CpfpInfo } from '@interfaces/node-api.interface'; +import { Acceleration, BlockExtended, CpfpInfo } from '@interfaces/node-api.interface'; import { Pool, TxAuditStatus } from '@components/transaction/transaction.component'; import { Observable } from 'rxjs'; +import { first, timeout } from 'rxjs/operators'; import { ETA } from '@app/services/eta.service'; import { MiningStats } from '@app/services/mining.service'; -import { Filter } from '@app/shared/filters.utils'; +import { Filter, TransactionFlags } from '@app/shared/filters.utils'; +import { StateService } from '@app/services/state.service'; +import { CacheService } from '@app/services/cache.service'; @Component({ selector: 'app-transaction-details', @@ -14,7 +17,7 @@ import { Filter } from '@app/shared/filters.utils'; standalone: false, changeDetection: ChangeDetectionStrategy.OnPush }) -export class TransactionDetailsComponent implements OnInit { +export class TransactionDetailsComponent implements OnChanges { @Input() network: string; @Input() tx: Transaction; @Input() isLoadingTx: boolean; @@ -45,9 +48,60 @@ export class TransactionDetailsComponent implements OnInit { @Output() accelerateClicked = new EventEmitter(); @Output() toggleCpfp$ = new EventEmitter(); - constructor() {} + acceleratorSavingsSats = 0; + officialMempoolSpace: boolean; - ngOnInit(): void {} + constructor( + private stateService: StateService, + private cacheService: CacheService, + private cd: ChangeDetectorRef, + ) { + this.officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; + } + + ngOnChanges(changes: SimpleChanges): void { + if (!changes.tx) { + return; + } + this.acceleratorSavingsSats = 0; + const hasIneligibleFlags = this.tx?.flags && (this.tx.flags & (TransactionFlags.inscription | TransactionFlags.sighash_none | TransactionFlags.sighash_single | TransactionFlags.sighash_acp)) > 0n; + if (this.officialMempoolSpace + && this.tx?.status?.confirmed + && !this.tx.acceleration && !this.accelerationInfo + && this.tx.weight <= 4000 + && !hasIneligibleFlags + && Math.min(...this.tx.vout.map(o => o.value)) <= 1000000 + ) { + const block = this.cacheService.getCachedBlock(this.tx.status.block_height); + if (block) { + this.calculateAcceleratorSavings(block); + } else { + const txid = this.tx.txid; + this.cacheService.loadBlock(this.tx.status.block_height); + this.cacheService.loadedBlocks$.pipe( + first(b => b.height === this.tx.status.block_height), + timeout({ each: 30000, with: () => [] }), + ).subscribe((block) => { + if (this.tx?.txid === txid) { + this.calculateAcceleratorSavings(block); + this.cd.markForCheck(); + } + }); + } + } + } + + calculateAcceleratorSavings(block: BlockExtended): void { + const minBlockRate = block?.extras?.feeRange?.[0]; + if (minBlockRate) { + const vsize = this.tx.weight / 4; + this.acceleratorSavingsSats = Math.max(0, this.tx.fee - Math.ceil(minBlockRate * vsize) - 75000); + } + } + + get showAcceleratorSavingsMsg(): boolean { + return this.acceleratorSavingsSats > 0 && this.cpfpInfo != null && !this.hasCpfp; + } onAccelerateClicked(): void { this.accelerateClicked.emit(true); From 51d0fd8c8353ab0724674a78802ae048fb6867bf Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 3 Apr 2026 01:35:45 +0000 Subject: [PATCH 230/369] fix copilot nits --- .../transaction-details/transaction-details.component.html | 2 +- .../transaction-details/transaction-details.component.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html index 89655db13..cf28d0960 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -48,7 +48,7 @@ @if (showAcceleratorSavingsMsg) { - Could have saved using Mempool Accelerator Pro™ + Could have saved using Mempool Accelerator Pro™ } @if (!isLoadingTx && !tx?.status?.confirmed && isAcceleration) { diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts index 20af39a58..4b3c7806e 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts @@ -64,7 +64,7 @@ export class TransactionDetailsComponent implements OnChanges { return; } this.acceleratorSavingsSats = 0; - const hasIneligibleFlags = this.tx?.flags && (this.tx.flags & (TransactionFlags.inscription | TransactionFlags.sighash_none | TransactionFlags.sighash_single | TransactionFlags.sighash_acp)) > 0n; + const hasIneligibleFlags = ((this.tx?.flags ?? 0n) & (TransactionFlags.inscription | TransactionFlags.sighash_none | TransactionFlags.sighash_single | TransactionFlags.sighash_acp)) > 0n; if (this.officialMempoolSpace && this.tx?.status?.confirmed && !this.tx.acceleration && !this.accelerationInfo @@ -93,14 +93,14 @@ export class TransactionDetailsComponent implements OnChanges { calculateAcceleratorSavings(block: BlockExtended): void { const minBlockRate = block?.extras?.feeRange?.[0]; - if (minBlockRate) { + if (minBlockRate !== undefined) { const vsize = this.tx.weight / 4; this.acceleratorSavingsSats = Math.max(0, this.tx.fee - Math.ceil(minBlockRate * vsize) - 75000); } } get showAcceleratorSavingsMsg(): boolean { - return this.acceleratorSavingsSats > 0 && this.cpfpInfo != null && !this.hasCpfp; + return this.acceleratorSavingsSats > 0 && this.cpfpInfo !== null && this.cpfpInfo !== undefined && !this.hasCpfp; } onAccelerateClicked(): void { From bbd077a7a8f33387c3fcdaf3632c61c2fdbf057e Mon Sep 17 00:00:00 2001 From: mononaut Date: Tue, 19 May 2026 12:17:15 +0000 Subject: [PATCH 231/369] fix (c) -> (r) --- .../transaction-details/transaction-details.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html index cf28d0960..2ec9ac28c 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -48,7 +48,7 @@ @if (showAcceleratorSavingsMsg) { - Could have saved using Mempool Accelerator Pro™ + Could have saved using Mempool Accelerator® Pro } @if (!isLoadingTx && !tx?.status?.confirmed && isAcceleration) { From 4fbd84df102fa8967820b16ff50042f3467590cc Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 19 May 2026 14:56:55 +0200 Subject: [PATCH 232/369] [account] footer CTA sends to account landing page --- .../components/global-footer/global-footer.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index 01909492c..5d7527b5e 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -40,7 +40,7 @@
} - @@ -52,7 +52,7 @@
} @if (!enterpriseInfo?.footer_img) { - From e0c84f4248c4ce159fd993a5fae80462ec4a10c4 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 22 May 2026 01:09:05 +0900 Subject: [PATCH 233/369] Upgrade nginx to 1.30.1 --- docker/frontend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index 6bbb2d5f3..a35f9a761 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.27.0-alpine +FROM nginx:1.30.1-alpine WORKDIR /patch From f8175b57701d10ed902e32420fe3cfdc1454dca3 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 22 May 2026 04:46:02 +0900 Subject: [PATCH 234/369] Update variables_hash_max_size --- production/nginx/nginx.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/production/nginx/nginx.conf b/production/nginx/nginx.conf index b81321153..1dd5b279c 100644 --- a/production/nginx/nginx.conf +++ b/production/nginx/nginx.conf @@ -17,6 +17,8 @@ http { include __NGINX_ETC_FOLDER__/mime.types; default_type application/octet-stream; + variables_hash_max_size 4096; + # HTTP basic configuration include mempool/production/nginx/http-basic.conf; include mempool/production/nginx/http-acl.conf; From fce35111aa577967cc205fe208d67e370628442a Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 22 May 2026 09:42:31 +0900 Subject: [PATCH 235/369] Move the variables config to the right place --- nginx.conf | 1 + production/nginx/nginx.conf | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nginx.conf b/nginx.conf index 670764e20..04fb11fba 100644 --- a/nginx.conf +++ b/nginx.conf @@ -10,6 +10,7 @@ events { } http { + variables_hash_max_size 4096; sendfile on; tcp_nopush on; tcp_nodelay on; diff --git a/production/nginx/nginx.conf b/production/nginx/nginx.conf index 1dd5b279c..b81321153 100644 --- a/production/nginx/nginx.conf +++ b/production/nginx/nginx.conf @@ -17,8 +17,6 @@ http { include __NGINX_ETC_FOLDER__/mime.types; default_type application/octet-stream; - variables_hash_max_size 4096; - # HTTP basic configuration include mempool/production/nginx/http-basic.conf; include mempool/production/nginx/http-acl.conf; From 640910d8aaa0712586a55c3abe5ae8add43e81e6 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 10:57:22 +0000 Subject: [PATCH 236/369] [ops] fix liquid assets sync --- production/liquid-sync-assets | 11 +++++++++++ production/mempool.crontab | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100755 production/liquid-sync-assets diff --git a/production/liquid-sync-assets b/production/liquid-sync-assets new file mode 100755 index 000000000..feaa1ae3c --- /dev/null +++ b/production/liquid-sync-assets @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh +set -e + +export NVM_DIR="$HOME/.nvm" +source "$NVM_DIR/nvm.sh" +nvm use v24.13.0 + +cd "$HOME/liquid/frontend" +npm run sync-assets + +rsync -av "$HOME/liquid/frontend/dist/mempool/browser/resources/assets"* "$HOME/public_html/liquid/resources/" diff --git a/production/mempool.crontab b/production/mempool.crontab index 196c2566e..705cf02c1 100644 --- a/production/mempool.crontab +++ b/production/mempool.crontab @@ -5,5 +5,4 @@ 37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 & # hourly liquid asset update -6 * * * * cd $HOME/liquid/frontend && npm run sync-assets && rsync -av $HOME/liquid/frontend/dist/mempool/browser/resources/assets* $HOME/public_html/liquid/resources/ >/dev/null 2>&1 - +6 * * * * $HOME/mempool/production/liquid-sync-assets >/dev/null 2>&1 From 051c4cf5b1c44d094e7180e0e02c3c86e6f08842 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 11:06:31 +0000 Subject: [PATCH 237/369] [ops] reduce liquid asset cache times --- production/nginx/server-common.conf | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/production/nginx/server-common.conf b/production/nginx/server-common.conf index ee5081ced..5532076ec 100644 --- a/production/nginx/server-common.conf +++ b/production/nginx/server-common.conf @@ -94,6 +94,24 @@ location /resources/customize. { expires 5m; } +# only cache liquid asset registry files for 1 hour +location = /resources/assets.json { + try_files $uri =404; + expires 1h; +} +location = /resources/assets.minimal.json { + try_files $uri =404; + expires 1h; +} +location = /resources/assets-testnet.json { + try_files $uri =404; + expires 1h; +} +location = /resources/assets-testnet.minimal.json { + try_files $uri =404; + expires 1h; +} + # cache /main.f40e91d908a068a2.js forever since they never change location ~* ^/.+\..+\.(js|css)$ { try_files /$lang/$uri /en-US/$uri =404; From e167577b4cefc86b3d1f84fc70c68ad26015c470 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 11:15:14 +0000 Subject: [PATCH 238/369] [ops] delete obsolete assets script --- production/mempool-update-assets | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100755 production/mempool-update-assets diff --git a/production/mempool-update-assets b/production/mempool-update-assets deleted file mode 100755 index 10debd09b..000000000 --- a/production/mempool-update-assets +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env zsh -set -e - -wget -O /mempool/public_html/mainnet/resources/assets.json https://raw.githubusercontent.com/blockstream/asset_registry_db/master/index.json -wget -O /mempool/public_html/mainnet/resources/assets.minimal.json https://raw.githubusercontent.com/blockstream/asset_registry_db/master/index.minimal.json - -wget -O /mempool/public_html/mainnet/resources/assets.json https://raw.githubusercontent.com/blockstream/asset_registry_testnet_db/master/index.json -wget -O /mempool/public_html/mainnet/resources/assets.minimal.json https://raw.githubusercontent.com/blockstream/asset_registry_testnet_db/master/index.minimal.json - -exit 0 From 73b64cc5fbc442d7d68b558387c6ecd7cb6c4820 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 16:13:02 +0000 Subject: [PATCH 239/369] use esplora registry api for liquid asset page --- .../app/components/asset/asset.component.ts | 22 ++++++++----------- .../src/app/interfaces/electrs.interface.ts | 4 ++++ frontend/src/app/services/assets.service.ts | 18 +++++++++++++-- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/components/asset/asset.component.ts b/frontend/src/app/components/asset/asset.component.ts index 5afcdbd1d..70793266b 100644 --- a/frontend/src/app/components/asset/asset.component.ts +++ b/frontend/src/app/components/asset/asset.component.ts @@ -7,7 +7,7 @@ import { WebsocketService } from '@app/services/websocket.service'; import { StateService } from '@app/services/state.service'; import { AudioService } from '@app/services/audio.service'; import { ApiService } from '@app/services/api.service'; -import { of, merge, Subscription, combineLatest } from 'rxjs'; +import { of, merge, Subscription, EMPTY } from 'rxjs'; import { SeoService } from '@app/services/seo.service'; import { environment } from '@environments/environment'; import { AssetsService } from '@app/services/assets.service'; @@ -82,30 +82,26 @@ export class AssetComponent implements OnInit, OnDestroy { ) .pipe( switchMap(() => { - return combineLatest([this.electrsApiService.getAsset$(this.assetString) + return this.electrsApiService.getAsset$(this.assetString) .pipe( catchError((err) => { this.isLoadingAsset = false; this.error = err; this.seoService.logSoft404(); console.log(err); - return of(null); - }) - ), this.assetsService.getAssetsMinimalJson$]) - .pipe( - take(1) - ); + return EMPTY; + }), + switchMap((asset) => this.assetsService.enrichLiquidAsset$(asset)), + take(1) + ); }) ); }) ) .pipe( - switchMap(([asset, assetsData]) => { + switchMap((asset) => { this.asset = asset; - this.assetContract = assetsData[this.asset.asset_id]; - if (!this.assetContract) { - this.assetContract = [null, '?', 'Unknown', 0]; - } + this.assetContract = [asset.entity?.domain || null, asset.ticker || '?', asset.name || 'Unknown', asset.precision || 0]; this.seoService.setDescription($localize`:@@meta.description.liquid.asset:Browse an overview of the Liquid asset ${this.assetContract[2]}:INTERPOLATION: (${this.assetContract[1]}:INTERPOLATION:): see issued amount, burned amount, circulating amount, related transactions, and more.`); this.blindedIssuance = this.asset.chain_stats.has_blinded_issuances || this.asset.mempool_stats.has_blinded_issuances; this.isNativeAsset = asset.asset_id === this.nativeAssetId; diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index 3eae7e391..ee4ef08af 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -206,6 +206,10 @@ export interface Asset { status: Status; chain_stats: AssetStats; mempool_stats: AssetStats; + name?: string; + ticker?: string; + precision?: number; + entity?: Entity; } export interface AssetExtended extends Asset { diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index 42afa9627..3320a52f6 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { map, shareReplay, switchMap } from 'rxjs/operators'; import { StateService } from '@app/services/state.service'; import { environment } from '@environments/environment'; -import { AssetExtended } from '@interfaces/electrs.interface'; +import { Asset, AssetExtended } from '@interfaces/electrs.interface'; @Injectable({ providedIn: 'root' @@ -69,4 +69,18 @@ export class AssetsService { this.getWorldMapJson$ = this.httpClient.get(apiBaseUrl + '/resources/worldmap.json').pipe(shareReplay()); } + + public enrichLiquidAsset$(asset: Asset): Observable { + if (asset.name || asset.ticker || asset.precision != null) { + return of(asset); + } else if (this.stateService.network === 'liquid' && asset.asset_id === environment.nativeAssetId) { + return of({ ...asset, name: 'Liquid Bitcoin', ticker: 'LBTC', precision: 8 }); + } else if (this.stateService.network === 'liquidtestnet' && asset.asset_id === environment.nativeTestAssetId) { + return of({ ...asset, name: 'Test Liquid Bitcoin', ticker: 'tLBTC', precision: 8 }); + } else { + return this.getAssetsJson$.pipe( + map((assets) => assets.objects[asset.asset_id] ? { ...asset, ...assets.objects[asset.asset_id] } : asset), + ); + } + } } From c5b9dc020e68e88aeb586a3a20e2f4a9258f06cd Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 16:37:35 +0000 Subject: [PATCH 240/369] use paginated esplora registry api for liquid assets list --- .../components/assets/assets.component.html | 2 +- .../app/components/assets/assets.component.ts | 55 +++++------------ .../src/app/interfaces/electrs.interface.ts | 8 +++ frontend/src/app/services/assets.service.ts | 60 ++++++++++++++++--- .../src/app/services/electrs-api.service.ts | 14 ++++- 5 files changed, 86 insertions(+), 53 deletions(-) diff --git a/frontend/src/app/components/assets/assets.component.html b/frontend/src/app/components/assets/assets.component.html index 30c6b7255..4685d7b49 100644 --- a/frontend/src/app/components/assets/assets.component.html +++ b/frontend/src/app/components/assets/assets.component.html @@ -18,7 +18,7 @@
- +

diff --git a/frontend/src/app/components/assets/assets.component.ts b/frontend/src/app/components/assets/assets.component.ts index f0f081f58..c34463ed7 100644 --- a/frontend/src/app/components/assets/assets.component.ts +++ b/frontend/src/app/components/assets/assets.component.ts @@ -1,13 +1,11 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { AssetsService } from '@app/services/assets.service'; -import { environment } from '@environments/environment'; import { UntypedFormGroup } from '@angular/forms'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { combineLatest, Observable } from 'rxjs'; -import { AssetExtended } from '@interfaces/electrs.interface'; +import { Observable } from 'rxjs'; import { SeoService } from '@app/services/seo.service'; -import { StateService } from '@app/services/state.service'; +import { AssetRegistryItem } from '@interfaces/electrs.interface'; @Component({ selector: 'app-assets', @@ -17,16 +15,14 @@ import { StateService } from '@app/services/state.service'; standalone: false, }) export class AssetsComponent implements OnInit { - nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 4 : 6; ellipses = window.matchMedia('(max-width: 670px)').matches ? false : true; - assets: AssetExtended[]; - assetsCache: AssetExtended[]; searchForm: UntypedFormGroup; - assets$: Observable; + assets$: Observable; page = 1; + totalAssets = 0; error: any; itemsPerPage: number; @@ -38,46 +34,23 @@ export class AssetsComponent implements OnInit { private route: ActivatedRoute, private router: Router, private seoService: SeoService, - private stateService: StateService, ) { } ngOnInit() { this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`); this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); - this.assets$ = combineLatest([ - this.assetsService.getAssetsJson$, - this.route.queryParams, - ]) + this.assets$ = this.route.queryParams .pipe( - take(1), - switchMap(([assets, qp]) => { - this.assets = assets.array; - - return this.route.queryParams - .pipe( - filter((queryParams) => { - const newPage = parseInt(queryParams.page, 10); - if (newPage !== this.page) { - return true; - } - return false; - }), - map((queryParams) => { - if (queryParams.page) { - const newPage = parseInt(queryParams.page, 10); - this.page = newPage; - } else { - this.page = 1; - } - return ''; - }) - ); - }), - map(() => { + switchMap((queryParams) => { + this.page = queryParams.page ? parseInt(queryParams.page, 10) : 1; const start = (this.page - 1) * this.itemsPerPage; - return this.assets.slice(start, this.itemsPerPage + start); - }) + return this.assetsService.getLiquidAssetsPage$(start, this.itemsPerPage); + }), + map((result) => { + this.totalAssets = result.total; + return result.assets; + }), ); } diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index ee4ef08af..a8e6d4483 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -225,6 +225,14 @@ export interface Entity { domain: string; } +export interface AssetRegistryItem { + asset_id: string; + name: string; + ticker?: string; + domain?: string; + entity?: Entity; +} + interface IssuanceTxin { txid: string; vin: number; diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index 3320a52f6..6d51c7f53 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -1,10 +1,11 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { Observable, of } from 'rxjs'; -import { map, shareReplay, switchMap } from 'rxjs/operators'; +import { Observable, of, throwError } from 'rxjs'; +import { catchError, map, shareReplay, switchMap } from 'rxjs/operators'; import { StateService } from '@app/services/state.service'; import { environment } from '@environments/environment'; -import { Asset, AssetExtended } from '@interfaces/electrs.interface'; +import { ElectrsApiService } from '@app/services/electrs-api.service'; +import { Asset, AssetExtended, AssetRegistryItem } from '@interfaces/electrs.interface'; @Injectable({ providedIn: 'root' @@ -15,19 +16,25 @@ export class AssetsService { getAssetsJson$: Observable<{ array: AssetExtended[]; objects: any}>; getAssetsMinimalJson$: Observable; getWorldMapJson$: Observable; + registryAvailable = true; + private apiBaseUrl = ''; constructor( private httpClient: HttpClient, private stateService: StateService, + private electrsApiService: ElectrsApiService, ) { - let apiBaseUrl = ''; - if (!this.stateService.isBrowser) { - apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT; + 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; } + this.stateService.networkChanged$.subscribe(() => { + this.registryAvailable = true; + }); this.getAssetsJson$ = this.stateService.networkChanged$ .pipe( - switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)), + switchMap(() => this.httpClient.get(`${this.apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)), map((rawAssets) => { const assets: AssetExtended[] = Object.values(rawAssets); @@ -56,7 +63,7 @@ export class AssetsService { ); this.getAssetsMinimalJson$ = this.stateService.networkChanged$ .pipe( - switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.minimal.json`)), + switchMap(() => this.httpClient.get(`${this.apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.minimal.json`)), map((assetsMinimal) => { if (this.stateService.network === 'liquidtestnet') { // Hard coding the Liquid Testnet native asset @@ -67,7 +74,7 @@ export class AssetsService { shareReplay(1), ); - this.getWorldMapJson$ = this.httpClient.get(apiBaseUrl + '/resources/worldmap.json').pipe(shareReplay()); + this.getWorldMapJson$ = this.httpClient.get(this.apiBaseUrl + '/resources/worldmap.json').pipe(shareReplay()); } public enrichLiquidAsset$(asset: Asset): Observable { @@ -83,4 +90,39 @@ export class AssetsService { ); } } + + public getLiquidAssetsPage$(startIndex: number, limit: number): Observable<{ assets: AssetRegistryItem[]; total: number }> { + return (this.registryAvailable ? this.electrsApiService.getLiquidAssetsRegistry$(startIndex, limit).pipe( + map((response) => { + const assets = response.body || []; + const total = parseInt(response.headers.get('X-Total-Results') || `${assets.length}`, 10); + if (!total && !assets.length) { + this.registryAvailable = false; + return null; + } + return { assets, total }; + }), + catchError((error) => { + if (![404, 501].includes(error.status)) { + return throwError(() => error); + } + this.registryAvailable = false; + return of(null); + }), + ) : of(null)).pipe( + switchMap((registryPage) => registryPage ? of(registryPage) : this.getAssetsJson$.pipe( + map((assets) => ({ + assets: assets.array.slice(startIndex, startIndex + limit), + total: assets.array.length, + })), + )), + map((page) => ({ + ...page, + assets: page.assets.map((asset) => ({ + ...asset, + entity: asset.entity || (asset.domain ? { domain: asset.domain } : undefined), + })), + })), + ); + } } diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index 831c9f167..687156f44 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { BehaviorSubject, Observable, catchError, filter, from, of, shareReplay, switchMap, take, tap } from 'rxjs'; -import { Transaction, Address, Outspend, Recent, Asset, ScriptHash, AddressTxSummary, Utxo } from '@interfaces/electrs.interface'; +import { Transaction, Address, Outspend, Recent, Asset, ScriptHash, AddressTxSummary, Utxo, AssetRegistryItem } from '@interfaces/electrs.interface'; import { StateService } from '@app/services/state.service'; import { BlockExtended } from '@interfaces/node-api.interface'; import { calcScriptHash$ } from '@app/bitcoin.utils'; @@ -228,6 +228,16 @@ export class ElectrsApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId); } + getLiquidAssetsRegistry$(startIndex: number, limit: number): Observable> { + const params = new HttpParams() + .set('start_index', startIndex) + .set('limit', limit) + .set('sort_field', 'name') + .set('sort_dir', 'asc'); + + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/assets/registry', { params, observe: 'response' }); + } + getAssetTransactions$(assetId: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId + '/txs'); } From 150a8fd87777fd77c9cf2cae30d503006b43b3d8 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 16:48:41 +0000 Subject: [PATCH 241/369] use esplora registry data for asset group pages --- .../asset-group/asset-group.component.ts | 27 ++++++++----------- frontend/src/app/services/assets.service.ts | 24 ++++++++++++++++- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.ts b/frontend/src/app/components/assets/asset-group/asset-group.component.ts index 4c3d45639..cb1932908 100644 --- a/frontend/src/app/components/assets/asset-group/asset-group.component.ts +++ b/frontend/src/app/components/assets/asset-group/asset-group.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; -import { combineLatest, Observable } from 'rxjs'; +import { from, Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { ApiService } from '@app/services/api.service'; import { AssetsService } from '@app/services/assets.service'; @@ -24,22 +24,17 @@ export class AssetGroupComponent implements OnInit { this.group$ = this.route.paramMap .pipe( switchMap((params: ParamMap) => { - return combineLatest([ - this.assetsService.getAssetsJson$, - this.apiService.getAssetGroup$(params.get('id')), - ]); + return this.apiService.getAssetGroup$(params.get('id')); + }), + switchMap((group) => { + return from(Promise.all(group.assets.map((assetId) => this.assetsService.getLiquidAssetData(assetId).catch(() => ({ asset_id: assetId }))))) + .pipe( + map((assets) => ({ + group: group, + assets: assets, + })) + ); }), - map(([assets, group]) => { - const items = []; - // @ts-ignore - for (const item of group.assets) { - items.push(assets.objects[item]); - } - return { - group: group, - assets: items - }; - }) ); } } diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index 6d51c7f53..eee61b98e 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { Observable, of, throwError } from 'rxjs'; +import { firstValueFrom, Observable, of, throwError } from 'rxjs'; import { catchError, map, shareReplay, switchMap } from 'rxjs/operators'; import { StateService } from '@app/services/state.service'; import { environment } from '@environments/environment'; @@ -77,6 +77,28 @@ export class AssetsService { this.getWorldMapJson$ = this.httpClient.get(this.apiBaseUrl + '/resources/worldmap.json').pipe(shareReplay()); } + public async getLiquidAssetData(assetId: string): Promise> { + if (this.stateService.network === 'liquid' && assetId === environment.nativeAssetId) { + return { asset_id: assetId, name: 'Liquid Bitcoin', ticker: 'LBTC', precision: 8 }; + } else if (this.stateService.network === 'liquidtestnet' && assetId === environment.nativeTestAssetId) { + return { asset_id: assetId, name: 'Test Liquid Bitcoin', ticker: 'tLBTC', precision: 8 }; + } else if (this.registryAvailable) { + try { + const apiAsset = await firstValueFrom(this.electrsApiService.getAsset$(assetId)); + if (apiAsset.name || apiAsset.ticker || apiAsset.precision != null) { + return apiAsset; + } + } catch (error: any) { + if (![404, 501].includes(error?.status)) { + throw error; + } + } + } + + const assets = await firstValueFrom(this.getAssetsJson$); + return assets.objects[assetId] || {}; + } + public enrichLiquidAsset$(asset: Asset): Observable { if (asset.name || asset.ticker || asset.precision != null) { return of(asset); From b16a3402703fee9c6624b2d1c33457f305da61ec Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 17:04:03 +0000 Subject: [PATCH 242/369] use esplora registry api for tx output asset annotations --- .../transactions-list.component.ts | 23 +++++++--- .../tx-bowtie-graph.component.ts | 19 +++++--- frontend/src/app/services/assets.service.ts | 46 +++++++++++++++++-- 3 files changed, 71 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index 4e40d4e67..45659a5b6 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -59,7 +59,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy { refreshOutspends$: ReplaySubject = new ReplaySubject(); refreshChannels$: ReplaySubject = new ReplaySubject(); showDetails$ = new BehaviorSubject(false); - assetsMinimal: any; + assetsMinimal: any = {}; transactionsLength: number = 0; inputRowLimit: number = 12; outputRowLimit: number = 12; @@ -118,12 +118,6 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy { } }); - if (this.network === 'liquid' || this.network === 'liquidtestnet') { - this.assetsService.getAssetsMinimalJson$.subscribe((assets) => { - this.assetsMinimal = assets; - }); - } - this.outspendsSubscription = merge( this.refreshOutspends$ .pipe( @@ -238,6 +232,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy { } this.transactionsLength = this.transactions.length; + this.loadLiquidAssetData(); if (!this.txPreview) { this.cacheService.setTxCache(this.transactions); @@ -370,6 +365,19 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy { } } + private loadLiquidAssetData(): void { + if (!this.isLiquid) { + return; + } + + this.assetsService.getLiquidAssetsMinimalData(this.transactions).then((assets) => { + this.assetsMinimal = assets; + this.ref.markForCheck(); + }).catch(() => { + this.ref.markForCheck(); + }); + } + updateAddressSimilarities(): void { if (!this.transactions || !this.transactions.length) { return; @@ -549,6 +557,7 @@ export class TransactionsListComponent implements OnInit, OnChanges, OnDestroy { for (const [index, vin] of temp.entries()) { newTx.vin[index].isInscription = vin.isInscription; } + this.loadLiquidAssetData(); this.ref.markForCheck(); }); } diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts index e9bdfb883..f2d55e98b 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts @@ -78,7 +78,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { zeroValueWidth = 60; zeroValueThickness = 20; hasLine: boolean; - assetsMinimal: any; + assetsMinimal: any = {}; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; outspendsSubscription: Subscription; @@ -116,12 +116,6 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { ngOnInit(): void { this.initGraph(); - if (this.network === 'liquid' || this.network === 'liquidtestnet') { - this.assetsService.getAssetsMinimalJson$.subscribe((assets) => { - this.assetsMinimal = assets; - }); - } - this.outspendsSubscription = merge( this.refreshOutspends$ .pipe( @@ -156,11 +150,22 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { ngOnChanges(): void { this.initGraph(); + this.loadLiquidAssetData(); if (!this.cached) { this.refreshOutspends$.next(this.tx.txid); } } + private loadLiquidAssetData(): void { + if (!this.isLiquid || !this.tx) { + return; + } + + this.assetsService.getLiquidAssetsMinimalData([this.tx]).then((assets) => { + this.assetsMinimal = assets; + }).catch(() => {}); + } + initGraph(): void { this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet'); this.gradient = this.gradientColors[this.network]; diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index eee61b98e..042d76531 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -5,19 +5,21 @@ import { catchError, map, shareReplay, switchMap } from 'rxjs/operators'; import { StateService } from '@app/services/state.service'; import { environment } from '@environments/environment'; import { ElectrsApiService } from '@app/services/electrs-api.service'; -import { Asset, AssetExtended, AssetRegistryItem } from '@interfaces/electrs.interface'; +import { Asset, AssetExtended, AssetRegistryItem, Transaction } from '@interfaces/electrs.interface'; @Injectable({ providedIn: 'root' }) export class AssetsService { - nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; - getAssetsJson$: Observable<{ array: AssetExtended[]; objects: any}>; getAssetsMinimalJson$: Observable; getWorldMapJson$: Observable; registryAvailable = true; private apiBaseUrl = ''; + private nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; + private assetsMinimalCache: any = { + [this.nativeAssetId]: this.stateService.network === 'liquid' ? [null, 'LBTC', 'Liquid Bitcoin', 8] : [null, 'tLBTC', 'Test Liquid Bitcoin', 8], + }; constructor( private httpClient: HttpClient, @@ -30,6 +32,10 @@ export class AssetsService { } this.stateService.networkChanged$.subscribe(() => { this.registryAvailable = true; + this.nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; + this.assetsMinimalCache = { + [this.nativeAssetId]: this.stateService.network === 'liquid' ? [null, 'LBTC', 'Liquid Bitcoin', 8] : [null, 'tLBTC', 'Test Liquid Bitcoin', 8], + }; }); this.getAssetsJson$ = this.stateService.networkChanged$ @@ -99,6 +105,40 @@ export class AssetsService { return assets.objects[assetId] || {}; } + public async getLiquidAssetMinimalData(assetId: string): Promise { + if (this.assetsMinimalCache[assetId]) { + return this.assetsMinimalCache[assetId]; + } + + const asset: any = await this.getLiquidAssetData(assetId); + if (asset.name || asset.ticker || asset.precision != null) { + this.assetsMinimalCache[assetId] = [asset.entity?.domain || asset.domain || null, asset.ticker, asset.name, asset.precision || 0]; + return this.assetsMinimalCache[assetId]; + } + return null; + } + + public async getLiquidAssetsMinimalData(transactions: Transaction[]): Promise { + const assetIds = new Set(); + for (const tx of transactions || []) { + for (const vin of tx.vin || []) { + if (vin.prevout?.asset && vin.prevout.asset !== this.nativeAssetId) { + assetIds.add(vin.prevout.asset); + } + } + for (const vout of tx.vout || []) { + if (vout.asset && vout.asset !== this.nativeAssetId) { + assetIds.add(vout.asset); + } + } + } + + const missingAssetIds = Array.from(assetIds).filter((assetId) => !this.assetsMinimalCache[assetId]); + await Promise.all(missingAssetIds.map((assetId) => this.getLiquidAssetMinimalData(assetId))); + + return this.assetsMinimalCache; + } + public enrichLiquidAsset$(asset: Asset): Observable { if (asset.name || asset.ticker || asset.precision != null) { return of(asset); From 88d3158ea9cdba4943ba0b653c5d3bddf34af9c5 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 17:07:31 +0000 Subject: [PATCH 243/369] use minimal liquid assets file for typeahead search --- .../assets/assets-nav/assets-nav.component.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts index bc5c40f3f..10242bf61 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts @@ -4,13 +4,19 @@ import { Router } from '@angular/router'; import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap'; import { merge, Observable, of, Subject } from 'rxjs'; import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators'; -import { AssetExtended } from '@interfaces/electrs.interface'; import { AssetsService } from '@app/services/assets.service'; import { SeoService } from '@app/services/seo.service'; import { StateService } from '@app/services/state.service'; import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe'; import { environment } from '@environments/environment'; +interface AssetSearchResult { + asset_id: string; + name: string; + ticker: string; + entity?: { domain: string }; +} + @Component({ selector: 'app-assets-nav', templateUrl: './assets-nav.component.html', @@ -21,10 +27,10 @@ export class AssetsNavComponent implements OnInit { @ViewChild('instance', {static: true}) instance: NgbTypeahead; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; searchForm: UntypedFormGroup; - assetsCache: AssetExtended[]; + assetsCache: AssetSearchResult[]; typeaheadSearchFn: ((text: Observable) => Observable); - formatterFn = (asset: AssetExtended) => asset.name + ' (' + asset.ticker + ')'; + formatterFn = (asset: AssetSearchResult) => asset.name + ' (' + asset.ticker + ')'; focus$ = new Subject(); click$ = new Subject(); @@ -62,15 +68,20 @@ export class AssetsNavComponent implements OnInit { if (!searchText.length) { return of([]); } - return this.assetsService.getAssetsJson$.pipe( + return this.assetsService.getAssetsMinimalJson$.pipe( map((assets) => { if (searchText.length ) { - const filteredAssets = assets.array.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 + const filteredAssets = Object.entries(assets).map(([assetId, assetData]: [string, any[]]) => ({ + asset_id: assetId, + entity: assetData[0] ? { domain: assetData[0] } : undefined, + ticker: assetData[1] || '', + name: assetData[2] || '', + })).filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1 || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1); return filteredAssets.slice(0, this.itemsPerPage); } else { - return assets.array.slice(0, this.itemsPerPage); + return []; } }) ); From 28d202c28b3a578c293c34d0c5e16d61233d878a Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 17:20:42 +0000 Subject: [PATCH 244/369] use new esplora asset search api --- .../assets/assets-nav/assets-nav.component.ts | 33 ++++--------------- frontend/src/app/services/assets.service.ts | 28 ++++++++++++++++ .../src/app/services/electrs-api.service.ts | 5 +++ 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts index 10242bf61..c31e6cbaa 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts @@ -3,19 +3,13 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms import { Router } from '@angular/router'; import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap'; import { merge, Observable, of, Subject } from 'rxjs'; -import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, filter, switchMap } from 'rxjs/operators'; import { AssetsService } from '@app/services/assets.service'; import { SeoService } from '@app/services/seo.service'; import { StateService } from '@app/services/state.service'; import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe'; import { environment } from '@environments/environment'; - -interface AssetSearchResult { - asset_id: string; - name: string; - ticker: string; - entity?: { domain: string }; -} +import { AssetRegistryItem } from '@interfaces/electrs.interface'; @Component({ selector: 'app-assets-nav', @@ -27,10 +21,10 @@ export class AssetsNavComponent implements OnInit { @ViewChild('instance', {static: true}) instance: NgbTypeahead; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; searchForm: UntypedFormGroup; - assetsCache: AssetSearchResult[]; + assetsCache: AssetRegistryItem[]; typeaheadSearchFn: ((text: Observable) => Observable); - formatterFn = (asset: AssetSearchResult) => asset.name + ' (' + asset.ticker + ')'; + formatterFn = (asset: AssetRegistryItem) => asset.name + ' (' + asset.ticker + ')'; focus$ = new Subject(); click$ = new Subject(); @@ -57,6 +51,7 @@ export class AssetsNavComponent implements OnInit { typeaheadSearch = (text$: Observable) => { const debouncedText$ = text$.pipe( + debounceTime(200), distinctUntilChanged() ); const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen())); @@ -68,23 +63,7 @@ export class AssetsNavComponent implements OnInit { if (!searchText.length) { return of([]); } - return this.assetsService.getAssetsMinimalJson$.pipe( - map((assets) => { - if (searchText.length ) { - const filteredAssets = Object.entries(assets).map(([assetId, assetData]: [string, any[]]) => ({ - asset_id: assetId, - entity: assetData[0] ? { domain: assetData[0] } : undefined, - ticker: assetData[1] || '', - name: assetData[2] || '', - })).filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 - || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1 - || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1); - return filteredAssets.slice(0, this.itemsPerPage); - } else { - return []; - } - }) - ); + return this.assetsService.searchLiquidAssets$(searchText, this.itemsPerPage); }), ); }; diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index 042d76531..687b2fe33 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -187,4 +187,32 @@ export class AssetsService { })), ); } + + public searchLiquidAssets$(searchText: string, limit: number): Observable { + const lowerSearchText = searchText.toLowerCase(); + return (this.registryAvailable ? this.electrsApiService.getLiquidAssetsRegistrySearch$(searchText).pipe( + catchError((error) => { + if (![404, 501].includes(error.status)) { + return throwError(() => error); + } + this.registryAvailable = false; + return of(null); + }), + ) : of(null)).pipe( + switchMap((registryAssets) => registryAssets ? of(registryAssets) : this.getAssetsMinimalJson$.pipe( + map((assets) => Object.entries(assets).map(([assetId, assetData]: [string, any[]]) => ({ + asset_id: assetId, + entity: assetData[0] ? { domain: assetData[0] } : undefined, + ticker: assetData[1] || '', + name: assetData[2] || '', + })).filter((asset) => asset.name.toLowerCase().indexOf(lowerSearchText) > -1 + || (asset.ticker || '').toLowerCase().indexOf(lowerSearchText) > -1 + || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(lowerSearchText) > -1)), + )), + map((assets) => assets.map((asset) => ({ + ...asset, + entity: asset.entity || (asset.domain ? { domain: asset.domain } : undefined), + })).slice(0, limit)), + ); + } } diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index 687156f44..c3d53f7da 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -238,6 +238,11 @@ export class ElectrsApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/assets/registry', { params, observe: 'response' }); } + getLiquidAssetsRegistrySearch$(query: string): Observable { + const params = new HttpParams().set('q', query); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/assets/registry/search', { params }); + } + getAssetTransactions$(assetId: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId + '/txs'); } From c144f0358fbfbc084d7593036ae8ab2527299b1d Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 25 May 2026 17:31:19 +0000 Subject: [PATCH 245/369] use new esplora registry search for main search form --- .../search-form/search-form.component.ts | 65 +++++++++++-------- .../search-results.component.html | 14 ++-- .../search-results.component.ts | 2 +- frontend/src/app/services/assets.service.ts | 17 ++++- 4 files changed, 63 insertions(+), 35 deletions(-) diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts index 5f2896b66..739d513c2 100644 --- a/frontend/src/app/components/search-form/search-form.component.ts +++ b/frontend/src/app/components/search-form/search-form.component.ts @@ -22,7 +22,6 @@ export class SearchFormComponent implements OnInit { @Input() hamburgerOpen = false; env: Env; network = ''; - assets: object = {}; pools: object[] = []; isSearching = false; isTypeaheading$ = new BehaviorSubject(false); @@ -96,13 +95,6 @@ export class SearchFormComponent implements OnInit { searchText: ['', Validators.required], }); - if (this.network === 'liquid' || this.network === 'liquidtestnet') { - this.assetsService.getAssetsMinimalJson$ - .subscribe((assets) => { - this.assets = assets; - }); - } - const searchText$ = this.searchForm.get('searchText').valueChanges .pipe( map((text) => { @@ -121,7 +113,8 @@ export class SearchFormComponent implements OnInit { return of([ [], { nodes: [], channels: [] }, - this.pools + this.pools, + [], ]); } this.isTypeaheading$.next(true); @@ -129,7 +122,8 @@ export class SearchFormComponent implements OnInit { return zip( this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))), [{ nodes: [], channels: [] }], - this.getMiningPools() + this.getMiningPools(), + this.getLiquidAssetSearch$(text), ); } return zip( @@ -138,7 +132,8 @@ export class SearchFormComponent implements OnInit { nodes: [], channels: [], }))), - this.getMiningPools() + this.getMiningPools(), + this.getLiquidAssetSearch$(text), ); }), map((result: any[]) => { @@ -159,7 +154,8 @@ export class SearchFormComponent implements OnInit { nodes: [], channels: [], }, - this.pools + this.pools, + [], ])) ] ).pipe( @@ -178,7 +174,7 @@ export class SearchFormComponent implements OnInit { addresses: [], nodes: [], channels: [], - liquidAsset: [], + liquidAssets: [], pools: [] }; } @@ -186,6 +182,7 @@ export class SearchFormComponent implements OnInit { const result = latestData[1]; const addressPrefixSearchResults = result[0]; const lightningResults = result[1]; + const liquidAssets = result[3]; // Do not show date and timestamp results for liquid const isNetworkBitcoin = this.network === '' || this.network === 'testnet' || this.network === 'testnet4' || this.network === 'signet'; @@ -198,8 +195,8 @@ export class SearchFormComponent implements OnInit { const matchesAddress = !matchesTxId && this.regexAddress.test(searchText); const publicKey = matchesAddress && searchText.startsWith('0'); const otherNetworks = findOtherNetworks(searchText, this.network as any || 'mainnet', this.env); - const liquidAsset = this.assets ? (this.assets[searchText] || []) : []; const pools = this.pools.filter(pool => pool['name'].toLowerCase().includes(searchText.toLowerCase())).slice(0, 10); + const hashQuickMatch = +(matchesBlockHeight || matchesBlockHash || (matchesTxId && !liquidAssets.length) || matchesAddress || matchesUnixTimestamp || matchesDateTime); if (matchesDateTime && searchText.indexOf('/') !== -1) { searchText = searchText.replace(/\//g, '-'); @@ -211,7 +208,7 @@ export class SearchFormComponent implements OnInit { return { searchText: searchText, - hashQuickMatch: +(matchesBlockHeight || matchesBlockHash || matchesTxId || matchesAddress || matchesUnixTimestamp || matchesDateTime), + hashQuickMatch: hashQuickMatch, blockHeight: matchesBlockHeight, dateTime: matchesDateTime, unixTimestamp: matchesUnixTimestamp, @@ -223,7 +220,7 @@ export class SearchFormComponent implements OnInit { otherNetworks: otherNetworks, nodes: lightningResults.nodes, channels: lightningResults.channels, - liquidAsset: liquidAsset, + liquidAssets: liquidAssets, pools: pools }; }) @@ -258,6 +255,8 @@ export class SearchFormComponent implements OnInit { } } else if (result.slug) { this.navigate('/mining/pool/', result.slug); + } else if (result.asset_id) { + this.navigate('/assets/asset/', result.asset_id); } } @@ -275,19 +274,24 @@ export class SearchFormComponent implements OnInit { } else if (this.regexTransaction.test(searchText)) { const matches = this.regexTransaction.exec(searchText); if (this.network === 'liquid' || this.network === 'liquidtestnet') { - if (this.assets[matches[0]]) { - this.navigate('/assets/asset/', matches[0]); - } - this.electrsApiService.getAsset$(matches[0]) - .subscribe( - () => { this.navigate('/assets/asset/', matches[0]); }, - () => { - this.electrsApiService.getBlock$(matches[0]) + this.assetsService.searchLiquidAssets$(matches[0], 1) + .pipe(catchError(() => of([]))) + .subscribe((assets) => { + if (assets[0]?.asset_id === matches[0]) { + this.navigate('/assets/asset/', matches[0]); + } else { + this.electrsApiService.getAsset$(matches[0]) .subscribe( - (block) => { this.navigate('/block/', matches[0], { state: { data: { block } } }); }, - () => { this.navigate('/tx/', matches[0]); }); + () => { this.navigate('/assets/asset/', matches[0]); }, + () => { + this.electrsApiService.getBlock$(matches[0]) + .subscribe( + (block) => { this.navigate('/block/', matches[0], { state: { data: { block } } }); }, + () => { this.navigate('/tx/', matches[0]); }); + } + ); } - ); + }); } else { this.navigate('/tx/', matches[0]); } @@ -347,4 +351,11 @@ export class SearchFormComponent implements OnInit { catchError(() => of([])) ); } + + getLiquidAssetSearch$(searchText: string): Observable { + if (this.network !== 'liquid' && this.network !== 'liquidtestnet') { + return of([]); + } + return this.assetsService.searchLiquidAssets$(searchText, 10).pipe(catchError(() => of([]))); + } } diff --git a/frontend/src/app/components/search-form/search-results/search-results.component.html b/frontend/src/app/components/search-form/search-results/search-results.component.html index 22e823265..79b4ad9bf 100644 --- a/frontend/src/app/components/search-form/search-results/search-results.component.html +++ b/frontend/src/app/components/search-form/search-results/search-results.component.html @@ -1,4 +1,4 @@ - -
+

This node hasn't seen any stale blocks yet!

@@ -124,6 +124,11 @@
+ +
diff --git a/frontend/src/app/components/stale-list/stale-list.component.ts b/frontend/src/app/components/stale-list/stale-list.component.ts index 4cae1f416..4bb97a8cb 100644 --- a/frontend/src/app/components/stale-list/stale-list.component.ts +++ b/frontend/src/app/components/stale-list/stale-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { BehaviorSubject, Observable, of } from 'rxjs'; -import { catchError, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, of, timer } from 'rxjs'; +import { catchError, map, retry, share, switchMap, tap } from 'rxjs/operators'; import { StaleTip, BlockExtended } from '@interfaces/node-api.interface'; import { ApiService } from '@app/services/api.service'; import { StateService } from '@app/services/state.service'; @@ -21,7 +21,7 @@ export class StaleList implements OnInit { isLoading = true; isLoadingMore = false; fullyLoaded = false; - error: any; + error: unknown = null; gradientColors = { '': ['var(--mainnet-alt)', 'var(--primary)'], @@ -41,17 +41,27 @@ export class StaleList implements OnInit { ngOnInit(): void { this.chainTips$ = this.loadMoreSubject.pipe( - distinctUntilChanged(), - switchMap(() => this.apiService.getStaleTips$(this.chainTips[this.chainTips.length - 1]?.height).pipe( + switchMap((height) => this.apiService.getStaleTips$(height).pipe( map((chainTips) => chainTips.filter((chainTip) => chainTip.status !== 'active') as StaleTip[]), + retry({ + count: 2, + delay: (err) => { + this.error = err; + return timer(1000); + }, + }), catchError((err) => { this.error = err; this.isLoading = false; this.isLoadingMore = false; - return of([]); + return of(null); }), )), tap((newChainTips) => { + if (newChainTips === null) { + return; + } + this.error = null; if (!newChainTips.length) { this.fullyLoaded = true; } else { @@ -79,11 +89,22 @@ export class StaleList implements OnInit { } loadMore(): void { - if (this.isLoadingMore || this.fullyLoaded) { + if (this.isLoading || this.isLoadingMore || this.fullyLoaded || this.error) { return; } this.isLoadingMore = true; - this.loadMoreSubject.next(this.chainTips[this.chainTips.length - 1]?.height); + const height = this.chainTips[this.chainTips.length - 1]?.height; + this.loadMoreSubject.next(height); + } + + retryLoadMore(): void { + if (this.isLoading || this.isLoadingMore || this.fullyLoaded) { + return; + } + this.error = null; + this.isLoadingMore = true; + const height = this.chainTips[this.chainTips.length - 1]?.height; + this.loadMoreSubject.next(height); } getBlockGradient(block: BlockExtended): string { From d3317a2e8b815dd11af04d13d3ddbd20901d232a Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 17 Jun 2026 10:58:18 +0000 Subject: [PATCH 290/369] replace staleTip cache with stale block cache --- backend/src/api/chain-tips.ts | 109 ++++++++++--------- backend/src/repositories/BlocksRepository.ts | 1 + 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/backend/src/api/chain-tips.ts b/backend/src/api/chain-tips.ts index 95c6cd47c..4620b2deb 100644 --- a/backend/src/api/chain-tips.ts +++ b/backend/src/api/chain-tips.ts @@ -31,20 +31,22 @@ export interface OrphanedBlock { class ChainTips { private chainTips: ChainTip[] = []; - private staleTips: Record = {}; + private validChainTips: ChainTip[] = []; // 'valid-fork' and 'valid-headers' only, in descending height order + private staleBlocks: Record = {}; private orphanedBlocks: { [hash: string]: OrphanedBlock } = {}; private blockCache: { [hash: string]: OrphanedBlock } = {}; private orphansByHeight: { [height: number]: OrphanedBlock[] } = {}; private indexingOrphanedBlocks = false; private indexingQueue: { blockhash?: string, block?: IEsploraApi.Block, tip: OrphanedBlock }[] = []; - private staleTipsCacheSize = 50; + private staleBlocksCacheSize = 50; private maxIndexingQueueSize = 100; /** @asyncSafe */ public async updateOrphanedBlocks(): Promise { try { this.chainTips = await bitcoinClient.getChainTips(); + this.validChainTips = this.chainTips.filter(tip => tip.status === 'valid-fork' || tip.status === 'valid-headers').sort((a, b) => b.height - a.height); const activeTipHeight = this.chainTips.find(tip => tip.status === 'active')?.height || (await bitcoinApi.$getBlockHeightTip()); let minIndexHeight = 0; @@ -122,13 +124,7 @@ class ChainTips { this.orphansByHeight[orphan.height].push(orphan); } - const heightsToKeep = new Set(this.chainTips.filter(tip => tip.status !== 'active').map(tip => tip.height)); - const heightsToRemove: number[] = Object.keys(this.staleTips).map(Number).filter(height => !heightsToKeep.has(height)); - for (const height of heightsToRemove) { - delete this.staleTips[height]; - } - - this.trimStaleTipsCache(); + this.trimStaleBlocksCache(); // index new orphaned blocks in the background void this.$indexOrphanedBlocks(); @@ -160,7 +156,7 @@ class ChainTips { } let staleBlock: BlockExtended | undefined; const alreadyIndexed = await BlocksSummariesRepository.$isSummaryIndexed(block.id); - const needToCache = Object.keys(this.staleTips).length < this.staleTipsCacheSize || block.height > Object.keys(this.staleTips).map(Number).sort((a, b) => b - a)[this.staleTipsCacheSize - 1]; + const needToCache = this.shouldCacheStaleBlock(block.id, block.height); if (!alreadyIndexed) { staleBlock = await blocks.$indexBlock(block.id, block, true); await blocks.$indexBlockSummary(block.id, block.height, true); @@ -171,16 +167,9 @@ class ChainTips { } if (staleBlock && needToCache) { - const canonicalBlock = await blocks.$indexBlockByHeight(staleBlock.height); - this.staleTips[staleBlock.height] = { - height: staleBlock.height, - hash: staleBlock.id, - branchlen: tip.branchlen, - status: tip.status, - stale: staleBlock, - canonical: canonicalBlock, - }; - this.trimStaleTipsCache(); + // ensure the canonical block is correctly indexed + await blocks.$indexBlockByHeight(staleBlock.height); + this.cacheStaleBlock(staleBlock); } } catch (e) { logger.err(`Failed to index orphaned block ${block?.id} at height ${block?.height}. Reason: ${e instanceof Error ? e.message : e}`); @@ -189,12 +178,42 @@ class ChainTips { this.indexingOrphanedBlocks = false; } - private trimStaleTipsCache(): void { - const staleTipHeights = Object.keys(this.staleTips).map(Number).sort((a, b) => b - a); - if (staleTipHeights.length > this.staleTipsCacheSize) { - const heightsToDiscard = staleTipHeights.slice(this.staleTipsCacheSize); - for (const height of heightsToDiscard) { - delete this.staleTips[height]; + private shouldCacheStaleBlock(hash: string, height: number): boolean { + // already cached + if (this.staleBlocks[hash]) { + return false; + } + + // cache is not full + const cachedBlocks = Object.values(this.staleBlocks); + if (cachedBlocks.length < this.staleBlocksCacheSize) { + return true; + } + + // otherwise cache if this block is newer than the oldest in the cache + const oldestCachedHeight = cachedBlocks.reduce((min, block) => Math.min(min, block.height), Infinity); + return height >= oldestCachedHeight; + } + + private cacheStaleBlock(block: BlockExtended): void { + this.staleBlocks[block.id] = block; + this.trimStaleBlocksCache(); + } + + // evict the oldest stale blocks until the cache is within the size limit + private trimStaleBlocksCache(): void { + // sort by height + const cachedBlocks = Object.values(this.staleBlocks).sort((a, b) => { + if (b.height !== a.height) { + return b.height - a.height; + } + // tie-break by hash + return a.id.localeCompare(b.id); + }); + // delete everything beyond the size limit + if (cachedBlocks.length > this.staleBlocksCacheSize) { + for (const block of cachedBlocks.slice(this.staleBlocksCacheSize)) { + delete this.staleBlocks[block.id]; } } } @@ -211,41 +230,27 @@ class ChainTips { return this.chainTips; } - public getStaleTips(): StaleTip[] { - return Object.values(this.staleTips).sort((a, b) => b.height - a.height); - } - - /** @asyncUnsafe */ + /** @asyncSafe */ public async $getStaleTipsPage(fromHeight: number | undefined, limit: number): Promise { - const cacheTips = this.getStaleTips(); - - const allStaleTips = this.chainTips.filter(chainTip => chainTip.status === 'valid-fork' || chainTip.status === 'valid-headers').sort((a, b) => b.height - a.height); - const staleTipsPage = fromHeight === undefined ? allStaleTips.slice(0, limit) : allStaleTips.filter(tip => tip.height < fromHeight).slice(0, limit); - - const cacheTipsMap = new Map(); - for (const cacheTip of cacheTips) { - cacheTipsMap.set(cacheTip.hash, cacheTip); - } + const start = fromHeight === undefined ? 0 : this.validChainTips.findIndex(tip => tip.height < fromHeight); + const staleTipsPage = start === -1 ? [] : this.validChainTips.slice(start, start + limit); + // hydrate tips with block data const tips: StaleTip[] = []; for (const staleTip of staleTipsPage) { - const cachedTip = cacheTipsMap.get(staleTip.hash); - if (cachedTip) { - tips.push(cachedTip); - continue; + // fetch blocks from caches if available, or DB otherwise + const canonical = blocks.getBlocks().find(block => block.height === staleTip.height) || await BlocksRepository.$getBlockByHeight(staleTip.height); + let stale: BlockExtended | null | undefined = this.staleBlocks[staleTip.hash]; + if (!stale) { + stale = await BlocksRepository.$getBlockByHash(staleTip.hash); } - - const canonical = await BlocksRepository.$getBlockByHeight(staleTip.height); - const stale = await BlocksRepository.$getBlockByHash(staleTip.hash); + // skip tips with missing block data if (!canonical || !stale) { continue; } tips.push({ - height: staleTip.height, - hash: staleTip.hash, - branchlen: staleTip.branchlen, - status: staleTip.status, + ...staleTip, stale, canonical, }); @@ -276,4 +281,4 @@ class ChainTips { } } -export default new ChainTips(); \ No newline at end of file +export default new ChainTips(); diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index a08c2776d..3a34e3a20 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -577,6 +577,7 @@ class BlocksRepository { /** * Get one block by hash + * @asyncSafe */ public async $getBlockByHash(hash: string): Promise { try { From 2db57928c474d9fd761c48d37898b5498bddc731 Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 17 Jun 2026 10:59:35 +0000 Subject: [PATCH 291/369] increase stale tip page size to 25 --- backend/src/api/bitcoin/bitcoin.routes.ts | 2 +- backend/src/api/chain-tips.ts | 28 +++++++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index b152869e4..e10157b1a 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -639,7 +639,7 @@ class BitcoinRoutes { } res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - const tips = await chainTips.$getStaleTipsPage(fromHeight, 10); + const tips = await chainTips.$getStaleTipsPage(fromHeight, 25); res.json(tips); } else { // Liquid handleError(req, res, 404, `This API is only available for Bitcoin networks`); diff --git a/backend/src/api/chain-tips.ts b/backend/src/api/chain-tips.ts index 4620b2deb..0d7b8713e 100644 --- a/backend/src/api/chain-tips.ts +++ b/backend/src/api/chain-tips.ts @@ -230,14 +230,31 @@ class ChainTips { return this.chainTips; } - /** @asyncSafe */ - public async $getStaleTipsPage(fromHeight: number | undefined, limit: number): Promise { + /** + * get paginated stale chain tips + * @param fromHeight - start height (exclusive) + * @param count - requested page size (target, but not strictly enforced) + * + * @asyncSafe + */ + public async $getStaleTipsPage(fromHeight: number | undefined, count: number): Promise { const start = fromHeight === undefined ? 0 : this.validChainTips.findIndex(tip => tip.height < fromHeight); - const staleTipsPage = start === -1 ? [] : this.validChainTips.slice(start, start + limit); + // no tips beyond the requested height, we can return early + if (start === -1) { + return []; + } - // hydrate tips with block data + // fill the response array with hydrated tip data const tips: StaleTip[] = []; - for (const staleTip of staleTipsPage) { + let lastHeight; + for (let index = start; index < this.validChainTips.length; index++) { + const staleTip = this.validChainTips[index]; + // stretch the page to include any remaining blocks at the last included height to avoid pagination gaps with a height-based cursor + if (tips.length >= count) { + if (staleTip.height !== lastHeight) { + break; + } + } // fetch blocks from caches if available, or DB otherwise const canonical = blocks.getBlocks().find(block => block.height === staleTip.height) || await BlocksRepository.$getBlockByHeight(staleTip.height); let stale: BlockExtended | null | undefined = this.staleBlocks[staleTip.hash]; @@ -254,6 +271,7 @@ class ChainTips { stale, canonical, }); + lastHeight = staleTip.height; } return tips; From f654704169fd54ec3633ff92491b6e2fd4ce7b49 Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 17 Jun 2026 11:59:54 +0000 Subject: [PATCH 292/369] fix missing canonical hash on stale block responses --- backend/src/repositories/BlocksRepository.ts | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 3a34e3a20..bb0e4f6c5 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -575,6 +575,31 @@ class BlocksRepository { } } + /** + * Get the canonical block hash at a given height + * @asyncSafe + */ + public async $getCanonicalBlockHashByHeight(height: number): Promise { + try { + const [rows]: any[] = await DB.query(` + SELECT hash + FROM blocks + WHERE height = ? AND stale = 0 + LIMIT 1`, + [height] + ); + + if (rows.length <= 0) { + return null; + } + + return rows[0].hash; + } catch (e) { + logger.err(`Cannot get canonical block hash at height ${height}. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } + /** * Get one block by hash * @asyncSafe @@ -1258,6 +1283,9 @@ class BlocksRepository { blk.mediantime = dbBlk.mediantime; blk.indexVersion = dbBlk.index_version; blk.stale = dbBlk.stale; + if (dbBlk.stale) { + blk.canonical = await this.$getCanonicalBlockHashByHeight(dbBlk.height) || undefined; + } // BlockExtension extras.totalFees = dbBlk.totalFees; extras.medianFee = dbBlk.medianFee; From eac66f3bd73df6d6baed952cad2ad0a33c117604 Mon Sep 17 00:00:00 2001 From: mononaut Date: Tue, 16 Jun 2026 05:35:27 +0000 Subject: [PATCH 293/369] bump angular to 20.3.25 --- frontend/package-lock.json | 460 +++++++++---------------------------- frontend/package.json | 24 +- 2 files changed, 120 insertions(+), 364 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 20d34d2b1..57406f287 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,17 +10,17 @@ "license": "GNU Affero General Public License v3.0", "dependencies": { "@angular-devkit/build-angular": "^20.3.25", - "@angular/animations": "^20.3.19", + "@angular/animations": "^20.3.25", "@angular/cli": "^20.3.25", - "@angular/common": "^20.3.19", - "@angular/compiler": "^20.3.19", - "@angular/core": "^20.3.19", - "@angular/forms": "^20.3.19", - "@angular/localize": "^20.3.19", - "@angular/platform-browser": "^20.3.19", - "@angular/platform-browser-dynamic": "^20.3.19", - "@angular/platform-server": "^20.3.19", - "@angular/router": "^20.3.19", + "@angular/common": "^20.3.25", + "@angular/compiler": "^20.3.25", + "@angular/core": "^20.3.25", + "@angular/forms": "^20.3.25", + "@angular/localize": "^20.3.25", + "@angular/platform-browser": "^20.3.25", + "@angular/platform-browser-dynamic": "^20.3.25", + "@angular/platform-server": "^20.3.25", + "@angular/router": "^20.3.25", "@angular/ssr": "^20.3.25", "@fortawesome/angular-fontawesome": "^3.0.0", "@fortawesome/fontawesome-common-types": "~6.7.2", @@ -41,8 +41,8 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/compiler-cli": "^20.3.19", - "@angular/language-service": "^20.3.19", + "@angular/compiler-cli": "^20.3.25", + "@angular/language-service": "^20.3.25", "@types/node": "^24.9.2", "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", @@ -354,23 +354,6 @@ } } }, - "node_modules/@angular-devkit/architect/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/architect/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -389,21 +372,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@angular-devkit/architect/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/architect/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -1021,23 +989,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1156,21 +1107,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -1307,23 +1243,6 @@ } } }, - "node_modules/@angular-devkit/schematics/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1342,21 +1261,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@angular-devkit/schematics/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/schematics/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -1367,9 +1271,10 @@ } }, "node_modules/@angular/animations": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.19.tgz", - "integrity": "sha512-/FjU9i7J58/yBURhgVSIiLDcuyOfJxAa0b7ZrOsx6P+FES+M2T2BKZl5V2NuiP2fDFtjsV7U+M/Z9UNUmeHCEw==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.25.tgz", + "integrity": "sha512-lQmti3tI85D525TjUVGqCNLzFxSUoZg+vgIyvuGJZPY0UU/o2S6KAxW6ObmcRotZZHNfenLHIxWgzamBDjIjuw==", + "deprecated": "@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1378,7 +1283,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.19" + "@angular/core": "20.3.25" } }, "node_modules/@angular/build": { @@ -2240,23 +2145,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@angular/cli/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular/cli/node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -2332,21 +2220,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@angular/cli/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular/cli/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -2434,9 +2307,9 @@ } }, "node_modules/@angular/common": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.19.tgz", - "integrity": "sha512-hcB1eUEN8LGcKGc4DlRJ+abS6AYfbEHDZKg8LnXNugkbwI6Ebyh2AUYTDhzZL2S4aH+C8biHKgSYHFCqieCRhA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.25.tgz", + "integrity": "sha512-rnRGcXbjet0DHgkRL4Dqxk21G2T4UypVfiTV/fay58H8w9U89PJ1L6gRmk8B/uyfpii/9r23cBwnpcguQykxYw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2445,14 +2318,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.19", + "@angular/core": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.19.tgz", - "integrity": "sha512-ETkgDKm0l2PuaBubgPJe0ccy8kE75DFu6/zKcz7TUuk3KrKF2OZAopbbjftsUSZGeCNvCdqHzjmcL6hQ6oAOwA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.25.tgz", + "integrity": "sha512-TSh6gVoQqlLPqWwsYMK0lfVEQYENQO+USzS+BHFXEHFfgBRap6qDpIUGnRdj0Y2PlaVJUVFbeq1855EZUPUEoA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2462,9 +2335,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.19.tgz", - "integrity": "sha512-ET/JjO8s62kAHfgIsGXlvW5VUwLqHm03q1y/2yD7aQW/WdDvssMsvZv7Knl440989vdOFemIGTMwVPakmWqRmA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.25.tgz", + "integrity": "sha512-iqxwVo5Pgzt3EfT49OZ6plxA6KKxwv7ixx1XNH7QRvaOJC9gmsPScWpx+LO7ZsVZdo/NkA+rnXDl0PauUgGciw==", "license": "MIT", "dependencies": { "@babel/core": "7.28.3", @@ -2484,7 +2357,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.19", + "@angular/compiler": "20.3.25", "typescript": ">=5.8 <6.0" }, "peerDependenciesMeta": { @@ -2522,9 +2395,9 @@ } }, "node_modules/@angular/core": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.19.tgz", - "integrity": "sha512-SYnwW+q51bQoPtGFoGovm1P5GK9fMEXsG0lGaEAUapjskblAYyX7hLlM/jgueSojv2SjhqNF8aXR+gjHLhZVNA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.25.tgz", + "integrity": "sha512-B4XnnR5jzikZDvZ4PjwjAWZMT14dxrKrmJdwa/n0yp7rMPkIJTKF6ZJMg4d1pLWLLSsc2oWHioN3UrWlGqIKnA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2533,7 +2406,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.19", + "@angular/compiler": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" }, @@ -2547,9 +2420,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.19.tgz", - "integrity": "sha512-WJotd+Lhl4FG2b0K+aQNyQDHhR515zKCuphjiUqEW7sifWrOQxANLKzPBngGrH75ayANFgPaDf7U3ZRIoblcQA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.25.tgz", + "integrity": "sha512-vGRo1LVPFo2Cu0k+QyDTlsBv5UbN0c3Et2YMS+43oyi1c4keocntBccOjLyM5C0kpMz4+pP81MqqYpAWu2k+TQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2558,16 +2431,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.19", - "@angular/core": "20.3.19", - "@angular/platform-browser": "20.3.19", + "@angular/common": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.19.tgz", - "integrity": "sha512-9J0XrAKXInz11KKyNMrMZmn2NSjVbxzt/DsAumbrzzixeZwiY7vDy2Kqw/LLFLi7IlfMQ/gznz/mCVVgUWI5Gg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.25.tgz", + "integrity": "sha512-3PZUwbDUVQXk9BLSiNUpDxTnRXGR3szwK9QFQaGDt8lhmLYaQLh3VJsY0FHDRghHZYIGR2P2MbVxGQHytF+IPw==", "dev": true, "license": "MIT", "engines": { @@ -2575,9 +2448,9 @@ } }, "node_modules/@angular/localize": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.19.tgz", - "integrity": "sha512-bXOwxzJUvHzmADI6czdjYnuBMil/UK3CW1dfbrC1MrlLtD0R7g4YZs08J7aWXd+/A4LmTPfkpZhI5ApxGAL0Tg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.25.tgz", + "integrity": "sha512-N4wmEBH44h58Av+1ivQ7LAe6Q0QP/2oBngvmnkO39AM6tadlnQGmaOvVzCUZe4BpOWYrrXQNiszSxGD3mUGgHg==", "license": "MIT", "dependencies": { "@babel/core": "7.28.3", @@ -2594,14 +2467,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.19", - "@angular/compiler-cli": "20.3.19" + "@angular/compiler": "20.3.25", + "@angular/compiler-cli": "20.3.25" } }, "node_modules/@angular/platform-browser": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.19.tgz", - "integrity": "sha512-TRZfatH1B/kreDwFRwtpLEurJQ6044qh6DWpvxzTbugaG5otLQJKTk+1z81/KsJwQqc1+24v+yuywc1LM7aq7w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.25.tgz", + "integrity": "sha512-0k06U/AJRQifGMLkcU3R9uEHWbuKEzkKMuKcGagXTrkeFvCG2Ub4JdsbcjFNWB2bspWgaxIMSceuj7c83U5wOA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2610,9 +2483,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.3.19", - "@angular/common": "20.3.19", - "@angular/core": "20.3.19" + "@angular/animations": "20.3.25", + "@angular/common": "20.3.25", + "@angular/core": "20.3.25" }, "peerDependenciesMeta": { "@angular/animations": { @@ -2621,9 +2494,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.19.tgz", - "integrity": "sha512-OgErw7wjcC+8yKF5h99hJq8x+tvc091wThfmdL5YC+U3HgRmUaNZFgB/jR7cb/NeeeC42QW5Vc0qoUTC9rMnLQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.25.tgz", + "integrity": "sha512-3Ku+IsN4tQPVBsw75SoLbLf7TsXAGL0rGPHSsyNYFhG2ZZeQuYNIAi8mc4cwz/qMDnuassHFrCxuLDgN6Yab5w==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2632,16 +2505,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.19", - "@angular/compiler": "20.3.19", - "@angular/core": "20.3.19", - "@angular/platform-browser": "20.3.19" + "@angular/common": "20.3.25", + "@angular/compiler": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25" } }, "node_modules/@angular/platform-server": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.19.tgz", - "integrity": "sha512-9STNB8Z5uYpaIgzfiJOH81c4CY2lM3oq/650+pdnjJsedxyEi+NAbnn5tF857Cd/N+43lR+OMolKgm0MJziHqw==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.25.tgz", + "integrity": "sha512-uOpLILe5QP9WLXhwshA3fbHc+Wx/4nrUBZNns/dw3t06bRHMbbkutF8eVs+n6Atl332y4mOcStzMX1ilBUSFHw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0", @@ -2651,17 +2524,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.19", - "@angular/compiler": "20.3.19", - "@angular/core": "20.3.19", - "@angular/platform-browser": "20.3.19", + "@angular/common": "20.3.25", + "@angular/compiler": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/router": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.19.tgz", - "integrity": "sha512-qHrMniHOsCJ4neZmcQVodjutJilyXAXk7EhLa931QyL0qyVKVomv6E0I3UFzRaC3ZeHc+hzBdU6C6bvMFKTl1g==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.25.tgz", + "integrity": "sha512-YIjLHWAufTaukNj15hEoys29e7XNhnCRsS1/95h/OqR69R3adbB8hV7ut7gO6XdXokriYqb4gtoUjoESxR+xFQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2670,9 +2543,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.19", - "@angular/core": "20.3.19", - "@angular/platform-browser": "20.3.19", + "@angular/common": "20.3.25", + "@angular/core": "20.3.25", + "@angular/platform-browser": "20.3.25", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -7582,23 +7455,6 @@ } } }, - "node_modules/@schematics/angular/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@schematics/angular/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7617,21 +7473,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@schematics/angular/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@schematics/angular/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19004,16 +18845,6 @@ "ajv": "^8.0.0" } }, - "chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "optional": true, - "peer": true, - "requires": { - "readdirp": "^4.0.1" - } - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -19024,13 +18855,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, - "readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "optional": true, - "peer": true - }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19288,16 +19112,6 @@ "ajv": "^8.0.0" } }, - "chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "optional": true, - "peer": true, - "requires": { - "readdirp": "^4.0.1" - } - }, "debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -19378,13 +19192,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, - "readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "optional": true, - "peer": true - }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -19461,16 +19268,6 @@ "ajv": "^8.0.0" } }, - "chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "optional": true, - "peer": true, - "requires": { - "readdirp": "^4.0.1" - } - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -19481,13 +19278,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, - "readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "optional": true, - "peer": true - }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19496,9 +19286,9 @@ } }, "@angular/animations": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.19.tgz", - "integrity": "sha512-/FjU9i7J58/yBURhgVSIiLDcuyOfJxAa0b7ZrOsx6P+FES+M2T2BKZl5V2NuiP2fDFtjsV7U+M/Z9UNUmeHCEw==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.25.tgz", + "integrity": "sha512-lQmti3tI85D525TjUVGqCNLzFxSUoZg+vgIyvuGJZPY0UU/o2S6KAxW6ObmcRotZZHNfenLHIxWgzamBDjIjuw==", "requires": { "tslib": "^2.3.0" } @@ -19897,16 +19687,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" }, - "chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "optional": true, - "peer": true, - "requires": { - "readdirp": "^4.0.1" - } - }, "cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -19954,13 +19734,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, - "readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "optional": true, - "peer": true - }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -20011,25 +19784,25 @@ } }, "@angular/common": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.19.tgz", - "integrity": "sha512-hcB1eUEN8LGcKGc4DlRJ+abS6AYfbEHDZKg8LnXNugkbwI6Ebyh2AUYTDhzZL2S4aH+C8biHKgSYHFCqieCRhA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.25.tgz", + "integrity": "sha512-rnRGcXbjet0DHgkRL4Dqxk21G2T4UypVfiTV/fay58H8w9U89PJ1L6gRmk8B/uyfpii/9r23cBwnpcguQykxYw==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.19.tgz", - "integrity": "sha512-ETkgDKm0l2PuaBubgPJe0ccy8kE75DFu6/zKcz7TUuk3KrKF2OZAopbbjftsUSZGeCNvCdqHzjmcL6hQ6oAOwA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.25.tgz", + "integrity": "sha512-TSh6gVoQqlLPqWwsYMK0lfVEQYENQO+USzS+BHFXEHFfgBRap6qDpIUGnRdj0Y2PlaVJUVFbeq1855EZUPUEoA==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.19.tgz", - "integrity": "sha512-ET/JjO8s62kAHfgIsGXlvW5VUwLqHm03q1y/2yD7aQW/WdDvssMsvZv7Knl440989vdOFemIGTMwVPakmWqRmA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.25.tgz", + "integrity": "sha512-iqxwVo5Pgzt3EfT49OZ6plxA6KKxwv7ixx1XNH7QRvaOJC9gmsPScWpx+LO7ZsVZdo/NkA+rnXDl0PauUgGciw==", "requires": { "@babel/core": "7.28.3", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -20057,31 +19830,31 @@ } }, "@angular/core": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.19.tgz", - "integrity": "sha512-SYnwW+q51bQoPtGFoGovm1P5GK9fMEXsG0lGaEAUapjskblAYyX7hLlM/jgueSojv2SjhqNF8aXR+gjHLhZVNA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.25.tgz", + "integrity": "sha512-B4XnnR5jzikZDvZ4PjwjAWZMT14dxrKrmJdwa/n0yp7rMPkIJTKF6ZJMg4d1pLWLLSsc2oWHioN3UrWlGqIKnA==", "requires": { "tslib": "^2.3.0" } }, "@angular/forms": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.19.tgz", - "integrity": "sha512-WJotd+Lhl4FG2b0K+aQNyQDHhR515zKCuphjiUqEW7sifWrOQxANLKzPBngGrH75ayANFgPaDf7U3ZRIoblcQA==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.25.tgz", + "integrity": "sha512-vGRo1LVPFo2Cu0k+QyDTlsBv5UbN0c3Et2YMS+43oyi1c4keocntBccOjLyM5C0kpMz4+pP81MqqYpAWu2k+TQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/language-service": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.19.tgz", - "integrity": "sha512-9J0XrAKXInz11KKyNMrMZmn2NSjVbxzt/DsAumbrzzixeZwiY7vDy2Kqw/LLFLi7IlfMQ/gznz/mCVVgUWI5Gg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.25.tgz", + "integrity": "sha512-3PZUwbDUVQXk9BLSiNUpDxTnRXGR3szwK9QFQaGDt8lhmLYaQLh3VJsY0FHDRghHZYIGR2P2MbVxGQHytF+IPw==", "dev": true }, "@angular/localize": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.19.tgz", - "integrity": "sha512-bXOwxzJUvHzmADI6czdjYnuBMil/UK3CW1dfbrC1MrlLtD0R7g4YZs08J7aWXd+/A4LmTPfkpZhI5ApxGAL0Tg==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.25.tgz", + "integrity": "sha512-N4wmEBH44h58Av+1ivQ7LAe6Q0QP/2oBngvmnkO39AM6tadlnQGmaOvVzCUZe4BpOWYrrXQNiszSxGD3mUGgHg==", "requires": { "@babel/core": "7.28.3", "@types/babel__core": "7.20.5", @@ -20090,34 +19863,34 @@ } }, "@angular/platform-browser": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.19.tgz", - "integrity": "sha512-TRZfatH1B/kreDwFRwtpLEurJQ6044qh6DWpvxzTbugaG5otLQJKTk+1z81/KsJwQqc1+24v+yuywc1LM7aq7w==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.25.tgz", + "integrity": "sha512-0k06U/AJRQifGMLkcU3R9uEHWbuKEzkKMuKcGagXTrkeFvCG2Ub4JdsbcjFNWB2bspWgaxIMSceuj7c83U5wOA==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.19.tgz", - "integrity": "sha512-OgErw7wjcC+8yKF5h99hJq8x+tvc091wThfmdL5YC+U3HgRmUaNZFgB/jR7cb/NeeeC42QW5Vc0qoUTC9rMnLQ==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.25.tgz", + "integrity": "sha512-3Ku+IsN4tQPVBsw75SoLbLf7TsXAGL0rGPHSsyNYFhG2ZZeQuYNIAi8mc4cwz/qMDnuassHFrCxuLDgN6Yab5w==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-server": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.19.tgz", - "integrity": "sha512-9STNB8Z5uYpaIgzfiJOH81c4CY2lM3oq/650+pdnjJsedxyEi+NAbnn5tF857Cd/N+43lR+OMolKgm0MJziHqw==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.25.tgz", + "integrity": "sha512-uOpLILe5QP9WLXhwshA3fbHc+Wx/4nrUBZNns/dw3t06bRHMbbkutF8eVs+n6Atl332y4mOcStzMX1ilBUSFHw==", "requires": { "tslib": "^2.3.0", "xhr2": "^0.2.0" } }, "@angular/router": { - "version": "20.3.19", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.19.tgz", - "integrity": "sha512-qHrMniHOsCJ4neZmcQVodjutJilyXAXk7EhLa931QyL0qyVKVomv6E0I3UFzRaC3ZeHc+hzBdU6C6bvMFKTl1g==", + "version": "20.3.25", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.25.tgz", + "integrity": "sha512-YIjLHWAufTaukNj15hEoys29e7XNhnCRsS1/95h/OqR69R3adbB8hV7ut7gO6XdXokriYqb4gtoUjoESxR+xFQ==", "requires": { "tslib": "^2.3.0" } @@ -22839,16 +22612,6 @@ "ajv": "^8.0.0" } }, - "chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "optional": true, - "peer": true, - "requires": { - "readdirp": "^4.0.1" - } - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -22859,13 +22622,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, - "readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "optional": true, - "peer": true - }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index 545f35077..829694835 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,17 +60,17 @@ }, "dependencies": { "@angular-devkit/build-angular": "^20.3.25", - "@angular/animations": "^20.3.19", + "@angular/animations": "^20.3.25", "@angular/cli": "^20.3.25", - "@angular/common": "^20.3.19", - "@angular/compiler": "^20.3.19", - "@angular/core": "^20.3.19", - "@angular/forms": "^20.3.19", - "@angular/localize": "^20.3.19", - "@angular/platform-browser": "^20.3.19", - "@angular/platform-browser-dynamic": "^20.3.19", - "@angular/platform-server": "^20.3.19", - "@angular/router": "^20.3.19", + "@angular/common": "^20.3.25", + "@angular/compiler": "^20.3.25", + "@angular/core": "^20.3.25", + "@angular/forms": "^20.3.25", + "@angular/localize": "^20.3.25", + "@angular/platform-browser": "^20.3.25", + "@angular/platform-browser-dynamic": "^20.3.25", + "@angular/platform-server": "^20.3.25", + "@angular/router": "^20.3.25", "@angular/ssr": "^20.3.25", "@fortawesome/angular-fontawesome": "^3.0.0", "@fortawesome/fontawesome-common-types": "~6.7.2", @@ -91,8 +91,8 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/compiler-cli": "^20.3.19", - "@angular/language-service": "^20.3.19", + "@angular/compiler-cli": "^20.3.25", + "@angular/language-service": "^20.3.25", "@types/node": "^24.9.2", "@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/parser": "^8.46.2", From d928feb663fd9cea86b9adec9221cda5cca85c80 Mon Sep 17 00:00:00 2001 From: rodribp Date: Mon, 22 Jun 2026 21:31:18 -0600 Subject: [PATCH 294/369] fix: goggles overflow-x with css mask on edges of .active-flags --- .../block-filters.component.html | 2 +- .../block-filters.component.scss | 60 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 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 e13ae975b..33dfb621d 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -6,7 +6,7 @@ -
+
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 268212729..df4b85372 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.scss +++ b/frontend/src/app/components/block-filters/block-filters.component.scss @@ -18,6 +18,41 @@ flex-wrap: wrap; row-gap: 0.25em; margin-left: 0.5em; + width: 100%; + + &.menu-open { + white-space: nowrap; + flex-wrap: nowrap; + overflow-x: auto; + -ms-overflow-style: none; + scrollbar-width: none; + margin-right: 0.5em; + + &::-webkit-scrollbar { + display: none; + } + + -webkit-mask-image: linear-gradient( + to right, + transparent 0, + #000 var(--fade-l), + #000 calc(100% - var(--fade-r)), + transparent 100% + ); + + mask-image: linear-gradient( + to right, + transparent 0, + #000 var(--fade-l), + #000 calc(100% - var(--fade-r)), + transparent 100% + ); + + animation: goggles-fade-l linear both, goggles-fade-r linear both; + animation-timeline: scroll(self inline); + animation-range:0 1.5em, 0 100%; + animation-duration: 1ms; + } } .info-badges { @@ -217,4 +252,27 @@ font-size: 0.5em; } } -} \ No newline at end of file +} + +@property --fade-l { + syntax: ""; + inherits: false; + initial-value: 0px; +} + +@property --fade-r { + syntax: ""; + inherits: false; + initial-value: 0px; +} + +@keyframes goggles-fade-l { + to { + --fade-l: 1em; + } +} +@keyframes goggles-fade-r { + from { + --fade-r: 1em; + } +} From c4debdd1f93925be3e594455ccbc8a20ae66b652 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 25 Jun 2026 19:12:25 +0800 Subject: [PATCH 295/369] [accelerator] update checkout component and documentation --- .../accelerate-checkout.component.html | 133 ++++++++---------- .../accelerate-checkout.component.scss | 4 + .../accelerate-checkout.component.ts | 31 ++-- .../accelerate-fee-graph.component.ts | 3 +- .../src/app/docs/api-docs/api-docs-data.ts | 14 +- 5 files changed, 91 insertions(+), 94 deletions(-) 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 e042b83f7..54fe158db 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -32,7 +32,6 @@ - @if (hasAccessToBalanceMode) { + @if (estimate.isProUser) { Next block market rate @@ -187,7 +186,7 @@ - + Estimated acceleration cost ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB @@ -208,7 +207,7 @@ - @if (hasAccessToBalanceMode) { + @if (estimate.isProUser) { Maximum acceleration cost } @else { Acceleration cost @@ -222,14 +221,14 @@ sats - + - + Available balance @@ -371,63 +370,69 @@
Payment to mempool.space for acceleration of txid {{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}
- @if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay)) { -
+
+ @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) {
-

Your account will be debited no more than {{ cost | number }} sats

-
- -
+

Pay  with

+ @if (canPayWithCashapp) { + + } + @if (canPayWithApplePay) { + @if (canPayWithCashapp) { } +
+ +
+ } + @if (canPayWithGooglePay) { + @if (canPayWithCashapp || canPayWithApplePay) { } +
+ +
+ } + @if (canPayWithCardOnFile) { + @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { } +
+ @if (isValidCardBrand(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) { + + } @else { + + } + {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }} +
+ }
-
- } @else { -
- @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) { -
-

Pay  with

- @if (canPayWithCashapp) { - - } - @if (canPayWithApplePay) { - @if (canPayWithCashapp) { } -
- + } + @if (((canPayWithBalance || canPayWithBitcoin) && (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile))) { +
+ —— OR —— +
+ } + @if (canPayWithBalance || canPayWithBitcoin) { +
+ @if (canPayWithBalance) { +

+ @if (estimate.isProUser) { + Your account will be debited no more than + } @else { + Use accelerator credits for + } +  {{ cost | number }} sats +

+
+
+ + Pay
- } - @if (canPayWithGooglePay) { - @if (canPayWithCashapp || canPayWithApplePay) { } -
- -
- } - @if (canPayWithCardOnFile) { - @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { } -
- @if (['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) { - - } @else { - - } - {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }} -
- } -
- } - @if ((canPayWithBitcoin && (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile))) { -
- —— OR —— -
- } - @if (canPayWithBitcoin) { -
+
+ } @else {

Pay {{ ((invoice?.btcDue * 100_000_000) || cost) | number }} sats with

-
- } -
- } + } +
+ } +
@@ -510,7 +515,7 @@
} @else if (step === 'cardonfile') {
- @if (['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) { + @if (isValidCardBrand(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) { } @else { @@ -637,20 +642,6 @@
- - @if (hasAccessToBalanceMode) { - - } @else { - - } - - Your transaction will be prioritized by up to {{ i | number : '1.1-1' }}% of miners. diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss index cc4edfb2b..ca0b07b63 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss @@ -26,6 +26,10 @@ hr { cursor: pointer; } +.purple-filter { + filter: brightness(0) saturate(100%) invert(30%) sepia(90%) saturate(5000%) hue-rotate(260deg); +} + .default-slot:not(:only-child) { display: none; } diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 4dff9ea38..6bd8e8c32 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -18,7 +18,7 @@ import { ThemeService } from '../../services/theme.service'; export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay' | 'cardOnFile'; export type AccelerationEstimate = { - hasAccess: boolean; + isProUser: boolean; txSummary: TxSummary; nextBlockFee: number; targetFeeRate: number; @@ -324,11 +324,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } return; } - if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { - if (this.isLoggedIn()) { - this.quoteError = `not_enough_balance`; - } - } if (this.estimate.unavailable) { this.quoteError = `temporarily_unavailable`; } @@ -973,6 +968,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return this.auth !== null; } + isValidCardBrand(brand: string | undefined): boolean { + return !!brand && ['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(brand); + } + /** * UI events */ @@ -1010,15 +1009,19 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return !!this.estimate?.availablePaymentMethods?.googlePay; } - get couldPayWithBalance(): boolean { - if (!this.hasAccessToBalanceMode) { + get couldPayWithCardOnFile(): boolean { + if (!this.cardOnFileEnabled || (!this.isProdDomain && !isDevMode())) { return false; } - return !!this.estimate?.availablePaymentMethods?.balance; + return this.isLoggedIn() && !!this.estimate?.availablePaymentMethods?.cardOnFile; + } + + get couldPayWithBalance(): boolean { + return this.isLoggedIn() && !!this.estimate?.availablePaymentMethods?.balance; } get couldPay(): boolean { - return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp || this.couldPayWithApplePay || this.couldPayWithGooglePay; + return this.couldPayWithBalance || this.couldPayWithBitcoin || this.couldPayWithCashapp || this.couldPayWithApplePay || this.couldPayWithGooglePay || this.couldPayWithCardOnFile; } get canOnlyPayWithBitcoin(): boolean { @@ -1095,7 +1098,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } get canPayWithBalance(): boolean { - if (!this.hasAccessToBalanceMode) { + if (!this.canUseBalance) { return false; } const paymentMethod = this.estimate?.availablePaymentMethods?.balance; @@ -1103,11 +1106,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } get canPay(): boolean { - return this.canPayWithBalance || this.canPayWithBitcoin || this.canPayWithCashapp || this.canPayWithApplePay || this.canPayWithGooglePay; + return this.canPayWithBalance || this.canPayWithBitcoin || this.canPayWithCashapp || this.canPayWithApplePay || this.canPayWithGooglePay || this.canPayWithCardOnFile; } - get hasAccessToBalanceMode(): boolean { - return this.isLoggedIn() && this.estimate?.hasAccess; + get canUseBalance(): boolean { + return this.isLoggedIn() && this.estimate?.userBalance > 0; } get timeSincePaid(): number { diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.ts index 86b075f1b..92e045cb5 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.ts @@ -22,7 +22,6 @@ interface GraphBar { export class AccelerateFeeGraphComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy { @Input() tx: Transaction; @Input() estimate: AccelerationEstimate; - @Input() showEstimate = false; @Input() maxRateOptions: RateOption[] = []; @Input() maxRateIndex: number = 0; @Output() setUserBid = new EventEmitter<{ fee: number, index: number }>(); @@ -89,7 +88,7 @@ export class AccelerateFeeGraphComponent implements OnInit, AfterViewInit, OnCha rate: option.rate, height: lastHeight, class: 'max', - label: this.showEstimate ? $localize`maximum` : $localize`:@@b484583f0ce10f3341ab36750d05271d9d22c9a1:Accelerated`.toLowerCase(), + label: this.estimate.isProUser ? $localize`maximum` : $localize`:@@b484583f0ce10f3341ab36750d05271d9d22c9a1:Accelerated`.toLowerCase(), active: option.index === this.maxRateIndex, rateIndex: option.index, fee: option.fee, 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 48af8f6d7..7544fd406 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -12015,7 +12015,7 @@ export const restApiDocsData = [ "fee": 12500 } ], - "hasAccess": false, + "isProUser": false, "availablePaymentMethods": { "bitcoin": { "enabled": true, @@ -12259,7 +12259,7 @@ export const restApiDocsData = [ category: 'accelerator-private', httpRequestMethod: 'POST', fragment: 'accelerator-top-up', - title: 'POST Top-up Accelerator (Pro)', + title: 'POST Top-up Accelerator Credits', description: { default: '

Generate a bitcoin invoice to top-up your accelerator credits. Minimum is 1,000,000 sats.

' }, @@ -12299,7 +12299,7 @@ export const restApiDocsData = [ category: 'accelerator-private', httpRequestMethod: 'GET', fragment: 'accelerator-top-up-history', - title: 'GET Top-up History (Pro)', + title: 'GET Credits Top-up History', description: { default: '

Returns a list of top ups the user has made as prepayment for the accelerator service.

' }, @@ -12347,9 +12347,9 @@ export const restApiDocsData = [ category: 'accelerator-private', httpRequestMethod: 'GET', fragment: 'accelerator-balance', - title: 'GET Available Balance (Pro)', + title: 'GET Available Credit Balance', description: { - default: '

Returns the user\'s currently available balance, currently locked funds, and total fees paid so far.

' + default: '

Returns the user\'s currently available accelerator credit balance, locked funds, and total fees paid so far.

' }, urlString: '/v1/services/accelerator/balance', showConditions: [''], @@ -12381,9 +12381,9 @@ export const restApiDocsData = [ category: 'accelerator-private', httpRequestMethod: 'POST', fragment: 'accelerator-accelerate', - title: 'POST Accelerate A Transaction (Pro)', + title: 'POST Accelerate A Transaction', description: { - default: '

Sends a request to accelerate a transaction.

' + default: '

Sends a request to accelerate a transaction using accelerator credits.

' }, urlString: '/v1/services/accelerator/accelerate', showConditions: [''], From 265fec4639de4d5d371238f0c0e818a72cd259c6 Mon Sep 17 00:00:00 2001 From: mononaut Date: Thu, 25 Jun 2026 11:54:34 +0000 Subject: [PATCH 296/369] parse dmnd stratum v2 tags --- backend/src/api/blocks.ts | 4 +++- backend/src/repositories/BlocksRepository.ts | 4 +++- backend/src/utils/bitcoin-script.ts | 25 +++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 0d3631928..be247f571 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -37,7 +37,7 @@ import { calculateGoodBlockCpfp } from './cpfp'; import blockProcessor, { BlockProcessingResult, detectTemplateAlgorithm, saveCpfpDataToCpfpSummary } from './block-processor'; import mempool from './mempool'; import CpfpRepository from '../repositories/CpfpRepository'; -import { parseDATUMTemplateCreator } from '../utils/bitcoin-script'; +import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script'; import database from '../database'; import { getBlockFirstSeenFromLogs, getOldestLogTimestampFromLogs, scanLogsForBlocksFirstSeen } from '../utils/file-read'; @@ -359,6 +359,8 @@ class Blocks { if (extras.pool.name === 'OCEAN') { extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw); + } else if (extras.pool.name === 'DMND') { + extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw); } } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index bb0e4f6c5..faa8fce74 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -14,7 +14,7 @@ import chainTips from '../api/chain-tips'; import blocks from '../api/blocks'; import BlocksAuditsRepository from './BlocksAuditsRepository'; import transactionUtils from '../api/transaction-utils'; -import { parseDATUMTemplateCreator } from '../utils/bitcoin-script'; +import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script'; import poolsUpdater from '../tasks/pools-updater'; interface DatabaseBlock { @@ -1374,6 +1374,8 @@ class BlocksRepository { if (extras.pool.name === 'OCEAN') { extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw); + } else if (extras.pool.name === 'DMND') { + extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw); } blk.extras = extras; diff --git a/backend/src/utils/bitcoin-script.ts b/backend/src/utils/bitcoin-script.ts index 117fa61a7..2a88477b8 100644 --- a/backend/src/utils/bitcoin-script.ts +++ b/backend/src/utils/bitcoin-script.ts @@ -224,4 +224,27 @@ export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null tagString = tagString.replace('\x00', ''); return tagString.split('\x0f').map((name) => name.replace(/[^a-zA-Z0-9 ]/g, '')); -} \ No newline at end of file +} + +/** Extracts miner names from a DMND coinbase transaction */ +export function parseDMNDTemplateCreator(coinbaseRaw: string): string[] { + try { + if (!coinbaseRaw || coinbaseRaw.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(coinbaseRaw)) { + return []; + } + + const bytes = Buffer.from(coinbaseRaw, 'hex'); + const blockHeightLength = bytes[0]; + const tagDelimiterIndex = 1 + blockHeightLength; + if (bytes.length <= tagDelimiterIndex || bytes[tagDelimiterIndex] !== 0x00) { + return []; + } + + const tagStart = tagDelimiterIndex + 1; + const tagEnd = bytes.indexOf(0x00, tagStart); + const tags = bytes.subarray(tagStart, tagEnd === -1 ? undefined : tagEnd); + return tags.toString('utf8').split('/').slice(1, -1); + } catch { + return []; + } +} From 2f767446349b91d97bbd7aad5c77b7fd729b824e Mon Sep 17 00:00:00 2001 From: mononaut Date: Fri, 26 Jun 2026 09:17:02 +0000 Subject: [PATCH 297/369] add miner json --- .../block/block-preview.component.html | 26 ++++--- .../app/components/block/block.component.html | 23 +++--- .../blockchain-blocks.component.html | 10 ++- .../stale-list/stale-list.component.html | 20 ++++-- .../transaction-details.component.html | 23 +++--- .../transaction/transaction.component.ts | 2 + .../src/app/interfaces/node-api.interface.ts | 2 + frontend/src/app/services/api.service.ts | 18 +++-- frontend/src/app/services/miners.service.ts | 71 +++++++++++++++++++ .../src/app/services/websocket.service.ts | 18 +++-- frontend/src/resources/miners.json | 8 +++ 11 files changed, 180 insertions(+), 41 deletions(-) create mode 100644 frontend/src/app/services/miners.service.ts create mode 100644 frontend/src/resources/miners.json diff --git a/frontend/src/app/components/block/block-preview.component.html b/frontend/src/app/components/block/block-preview.component.html index d7998c856..6b59974df 100644 --- a/frontend/src/app/components/block/block-preview.component.html +++ b/frontend/src/app/components/block/block-preview.component.html @@ -53,15 +53,23 @@ Miner - - @if (block.extras.pool.minerNames[1].length > 16) { - {{ block.extras.pool.minerNames[1].slice(0, 15) }}… - } @else { - {{ block.extras.pool.minerNames[1] }} - } - - - {{ block.extras.pool.name }} + + + {{ block.extras.pool.minerName }} + + {{ block.extras.pool.name }} + + + + @if (block.extras.pool.minerNames[1].length > 16) { + {{ block.extras.pool.minerNames[1].slice(0, 15) }}… + } @else { + {{ block.extras.pool.minerNames[1] }} + } + + + {{ block.extras.pool.name }} + diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index be0a094ac..f076debc5 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -188,14 +188,21 @@ Miner - - @if (block.extras.pool.minerNames[1].length > 16) { - {{ block.extras.pool.minerNames[1].slice(0, 15) }}… - } @else { - {{ block.extras.pool.minerNames[1] }} - } - - + + + {{ block.extras.pool.minerName }} + + + + + @if (block.extras.pool.minerNames[1].length > 16) { + {{ block.extras.pool.minerNames[1].slice(0, 15) }}… + } @else { + {{ block.extras.pool.minerNames[1] }} + } + + + diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index fee6516aa..82138d98e 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -79,8 +79,14 @@
- - {{ block.extras.pool.minerNames[1] }} + + + {{ block.extras.pool.minerName }} + + + + {{ block.extras.pool.minerNames[1] }} + diff --git a/frontend/src/app/components/stale-list/stale-list.component.html b/frontend/src/app/components/stale-list/stale-list.component.html index d761e7c5d..5394d677c 100644 --- a/frontend/src/app/components/stale-list/stale-list.component.html +++ b/frontend/src/app/components/stale-list/stale-list.component.html @@ -57,8 +57,14 @@
- - {{ chainTip.stale.extras.pool.minerNames[1] }} + + + {{ chainTip.stale.extras.pool.minerName }} + + + + {{ chainTip.stale.extras.pool.minerNames[1] }} + @@ -103,8 +109,14 @@
- - {{ chainTip.canonical.extras.pool.minerNames[1] }} + + + {{ chainTip.canonical.extras.pool.minerName }} + + + + {{ chainTip.canonical.extras.pool.minerNames[1] }} + diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html index 7d1ede2d8..001e57bc9 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -333,14 +333,21 @@ @if (pool) { - - @if (pool.minerNames[1].length > 16) { - {{ pool.minerNames[1].slice(0, 15) }}… - } @else { - {{ pool.minerNames[1] }} - } - - + + + {{ pool.minerName }} + + + + + @if (pool.minerNames[1].length > 16) { + {{ pool.minerNames[1].slice(0, 15) }}… + } @else { + {{ pool.minerNames[1] }} + } + + + } @else { diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index de4936f12..8c7892c37 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -45,6 +45,8 @@ export interface Pool { id: number; name: string; slug: string; + minerName?: string; + minerSlug?: string; minerNames: string[] | null; } diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index a76a2d2be..92450246a 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -224,6 +224,8 @@ export interface BlockExtension { id: number; name: string; slug: string; + minerName?: string; + minerSlug?: string; minerNames: string[] | null; } orphans?: { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 7d1cc77bb..d05b02c66 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -2,13 +2,14 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult, WalletAddress, Treasury, SubmitPackageResult, ChainTip, StaleTip } from '@interfaces/node-api.interface'; -import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs'; +import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, switchMap, take, tap } from 'rxjs'; import { StateService } from '@app/services/state.service'; import { Transaction } from '@interfaces/electrs.interface'; import { Conversion } from '@app/services/price.service'; import { StorageService } from '@app/services/storage.service'; import { WebsocketResponse } from '@interfaces/websocket.interface'; import { TxAuditStatus } from '@components/transaction/transaction.component'; +import { MinersService } from '@app/services/miners.service'; @Injectable({ providedIn: 'root' @@ -24,7 +25,8 @@ export class ApiService { constructor( private httpClient: HttpClient, private stateService: StateService, - private storageService: StorageService + private storageService: StorageService, + private minersService: MinersService, ) { this.apiBaseUrl = ''; // use relative URL by default if (!stateService.isBrowser) { // except when inside AU SSR process @@ -173,7 +175,9 @@ export class ApiService { } getStaleTips$(height?: number): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips' + (height !== undefined ? `/${height}` : ``)); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips' + (height !== undefined ? `/${height}` : ``)).pipe( + switchMap((staleTips) => this.minersService.applyStaleTipsMinerDetails$(staleTips)) + ); } liquidPegs$(): Observable { @@ -308,6 +312,8 @@ export class ApiService { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/blocks` + (fromHeight !== undefined ? `/${fromHeight}` : '') + ).pipe( + switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks)) ); } @@ -315,11 +321,15 @@ export class ApiService { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks` + (from !== undefined ? `/${from}` : ``) + ).pipe( + switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks)) ); } getBlock$(hash: string): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash).pipe( + switchMap((block) => this.minersService.applyBlockMinerDetails$(block)) + ); } getBlockDataFromTimestamp$(timestamp: number): Observable { diff --git a/frontend/src/app/services/miners.service.ts b/frontend/src/app/services/miners.service.ts new file mode 100644 index 000000000..53b8a52b5 --- /dev/null +++ b/frontend/src/app/services/miners.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { map, shareReplay } from 'rxjs/operators'; +import { BlockExtended, StaleTip } from '@interfaces/node-api.interface'; +import { StateService } from '@app/services/state.service'; + +interface Miner { + name: string; + slug: string; + tags: string[]; +} + +@Injectable({ + providedIn: 'root' +}) +export class MinersService { + private apiBaseUrl = ''; + private miners$: Observable; + + constructor( + private httpClient: HttpClient, + private stateService: StateService, + ) { + if (!stateService.isBrowser) { + this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT; + } + this.miners$ = this.httpClient.get(this.apiBaseUrl + '/resources/miners.json').pipe(shareReplay(1)); + } + + public applyBlockMinerDetails$(block: BlockExtended): Observable { + if (!block?.extras?.pool?.minerNames?.length) { + return of(block); + } + return this.miners$.pipe(map((miners) => this.applyBlockMinerDetails(block, miners))); + } + + public applyBlocksMinerDetails$(blocks: BlockExtended[]): Observable { + if (!blocks?.some((block) => block?.extras?.pool?.minerNames?.length)) { + return of(blocks); + } + return this.miners$.pipe(map((miners) => blocks.map((block) => this.applyBlockMinerDetails(block, miners)))); + } + + public applyStaleTipsMinerDetails$(staleTips: StaleTip[]): Observable { + if (!staleTips?.some((staleTip) => staleTip.stale?.extras?.pool?.minerNames?.length || staleTip.canonical?.extras?.pool?.minerNames?.length)) { + return of(staleTips); + } + return this.miners$.pipe( + map((miners) => staleTips.map((staleTip) => { + this.applyBlockMinerDetails(staleTip.stale, miners); + this.applyBlockMinerDetails(staleTip.canonical, miners); + return staleTip; + })) + ); + } + + private applyBlockMinerDetails(block: BlockExtended, miners: Miner[]): BlockExtended { + const minerNames = block?.extras?.pool?.minerNames; + if (!minerNames?.length) { + return block; + } + + const miner = miners.find((minerEntry) => minerEntry.tags.some((tag) => minerNames.includes(tag))); + if (miner) { + block.extras.pool.minerName = miner.name; + block.extras.pool.minerSlug = miner.slug; + } + return block; + } +} diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 8979ce4cf..dcb59f3d8 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -9,6 +9,7 @@ import { take } from 'rxjs/operators'; import { TransferState, makeStateKey } from '@angular/core'; import { CacheService } from '@app/services/cache.service'; import { uncompressDeltaChange, uncompressTx } from '@app/shared/common.utils'; +import { MinersService } from '@app/services/miners.service'; const OFFLINE_RETRY_AFTER_MS = 2000; const OFFLINE_PING_CHECK_AFTER_MS = 30000; @@ -51,6 +52,7 @@ export class WebsocketService { private apiService: ApiService, private transferState: TransferState, private cacheService: CacheService, + private minersService: MinersService, ) { if (!this.stateService.isBrowser) { // @ts-ignore @@ -356,9 +358,11 @@ export class WebsocketService { if (response.blocks && response.blocks.length) { const blocks = response.blocks; - this.stateService.resetBlocks(blocks); - const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight); - this.stateService.updateChainTip(maxHeight); + this.minersService.applyBlocksMinerDetails$(blocks).subscribe((mappedBlocks) => { + this.stateService.resetBlocks(mappedBlocks); + const maxHeight = mappedBlocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight); + this.stateService.updateChainTip(maxHeight); + }); } if (response.tx) { @@ -371,9 +375,11 @@ export class WebsocketService { if (response.block) { if (response.block.height === this.stateService.latestBlockHeight + 1) { - this.stateService.updateChainTip(response.block.height); - this.stateService.addBlock(response.block); - this.stateService.txConfirmed$.next([response.txConfirmed, response.block]); + this.minersService.applyBlockMinerDetails$(response.block).subscribe((block) => { + this.stateService.updateChainTip(block.height); + this.stateService.addBlock(block); + this.stateService.txConfirmed$.next([response.txConfirmed, block]); + }); } else if (response.block.height > this.stateService.latestBlockHeight + 1) { reinitBlocks = true; } diff --git a/frontend/src/resources/miners.json b/frontend/src/resources/miners.json new file mode 100644 index 000000000..7034597c8 --- /dev/null +++ b/frontend/src/resources/miners.json @@ -0,0 +1,8 @@ +[ + { + "name": "GoMining", + "tags": ["GM", "GoMining"], + "link": "https://gomining.com/", + "slug": "gomining" + } +] \ No newline at end of file From 7ca809a074e23a91381675dd2989eff491387ebd Mon Sep 17 00:00:00 2001 From: mononaut Date: Fri, 26 Jun 2026 12:40:33 +0000 Subject: [PATCH 298/369] miner json fixes --- .../components/block/block-preview.component.html | 2 +- .../src/app/components/block/block.component.html | 2 +- .../blockchain-blocks.component.html | 2 +- .../stale-list/stale-list.component.html | 4 ++-- .../transaction-details.component.html | 2 +- frontend/src/app/services/miners.service.ts | 9 ++++++--- frontend/src/app/services/websocket.service.ts | 14 ++++++++++---- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/block/block-preview.component.html b/frontend/src/app/components/block/block-preview.component.html index 6b59974df..d844f5181 100644 --- a/frontend/src/app/components/block/block-preview.component.html +++ b/frontend/src/app/components/block/block-preview.component.html @@ -53,7 +53,7 @@ Miner - + {{ block.extras.pool.minerName }} diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index f076debc5..7e5c7af72 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -188,7 +188,7 @@ Miner - + {{ block.extras.pool.minerName }} diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index 82138d98e..0ba86cff1 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -79,7 +79,7 @@
- + {{ block.extras.pool.minerName }} diff --git a/frontend/src/app/components/stale-list/stale-list.component.html b/frontend/src/app/components/stale-list/stale-list.component.html index 5394d677c..381159c6e 100644 --- a/frontend/src/app/components/stale-list/stale-list.component.html +++ b/frontend/src/app/components/stale-list/stale-list.component.html @@ -57,7 +57,7 @@
- + {{ chainTip.stale.extras.pool.minerName }} @@ -109,7 +109,7 @@
- + {{ chainTip.canonical.extras.pool.minerName }} diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html index 001e57bc9..a1729dfec 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -333,7 +333,7 @@ @if (pool) { - + {{ pool.minerName }} diff --git a/frontend/src/app/services/miners.service.ts b/frontend/src/app/services/miners.service.ts index 53b8a52b5..7e82668de 100644 --- a/frontend/src/app/services/miners.service.ts +++ b/frontend/src/app/services/miners.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; -import { map, shareReplay } from 'rxjs/operators'; +import { catchError, map, shareReplay } from 'rxjs/operators'; import { BlockExtended, StaleTip } from '@interfaces/node-api.interface'; import { StateService } from '@app/services/state.service'; @@ -25,7 +25,10 @@ export class MinersService { if (!stateService.isBrowser) { this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT; } - this.miners$ = this.httpClient.get(this.apiBaseUrl + '/resources/miners.json').pipe(shareReplay(1)); + this.miners$ = this.httpClient.get(this.apiBaseUrl + '/resources/miners.json').pipe( + catchError(() => of([])), + shareReplay(1) + ); } public applyBlockMinerDetails$(block: BlockExtended): Observable { @@ -62,7 +65,7 @@ export class MinersService { } const miner = miners.find((minerEntry) => minerEntry.tags.some((tag) => minerNames.includes(tag))); - if (miner) { + if (miner?.name && miner.slug) { block.extras.pool.minerName = miner.name; block.extras.pool.minerSlug = miner.slug; } diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index dcb59f3d8..0db69de1d 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -3,9 +3,9 @@ import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; import { WebsocketResponse } from '@interfaces/websocket.interface'; import { StateService } from '@app/services/state.service'; import { Transaction } from '@interfaces/electrs.interface'; -import { firstValueFrom, Subscription } from 'rxjs'; +import { firstValueFrom, of, Subscription } from 'rxjs'; import { ApiService } from '@app/services/api.service'; -import { take } from 'rxjs/operators'; +import { catchError, take } from 'rxjs/operators'; import { TransferState, makeStateKey } from '@angular/core'; import { CacheService } from '@app/services/cache.service'; import { uncompressDeltaChange, uncompressTx } from '@app/shared/common.utils'; @@ -358,7 +358,10 @@ export class WebsocketService { if (response.blocks && response.blocks.length) { const blocks = response.blocks; - this.minersService.applyBlocksMinerDetails$(blocks).subscribe((mappedBlocks) => { + this.minersService.applyBlocksMinerDetails$(blocks).pipe( + take(1), + catchError(() => of(blocks)) + ).subscribe((mappedBlocks) => { this.stateService.resetBlocks(mappedBlocks); const maxHeight = mappedBlocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight); this.stateService.updateChainTip(maxHeight); @@ -375,7 +378,10 @@ export class WebsocketService { if (response.block) { if (response.block.height === this.stateService.latestBlockHeight + 1) { - this.minersService.applyBlockMinerDetails$(response.block).subscribe((block) => { + this.minersService.applyBlockMinerDetails$(response.block).pipe( + take(1), + catchError(() => of(response.block)) + ).subscribe((block) => { this.stateService.updateChainTip(block.height); this.stateService.addBlock(block); this.stateService.txConfirmed$.next([response.txConfirmed, block]); From 446b5e84c3e2e89377982262cdd229592bcb0dd1 Mon Sep 17 00:00:00 2001 From: wiz Date: Sat, 27 Jun 2026 14:52:21 +0900 Subject: [PATCH 299/369] Remove 'see more details here' link from FAQ --- frontend/src/app/docs/api-docs/api-docs.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c0b2bc38b..1eeb01f87 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -330,7 +330,7 @@ -

Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it. See more details here.

As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.

+

Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it.

As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.

From 7b283de184fa4134e1b5f8b08644c5dc3bedb987 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 27 Jun 2026 18:39:06 +0800 Subject: [PATCH 300/369] [accelerator] wait for estimate to resolve before taking payment --- .../accelerate-checkout.component.ts | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 4dff9ea38..33539205b 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges, HostListener } from '@angular/core'; -import { Subscription, tap, of, catchError, Observable, switchMap } from 'rxjs'; +import { Subscription, tap, of, catchError, Observable, switchMap, Subject, ReplaySubject, filter, take, takeUntil, timeout } from 'rxjs'; import { ServicesApiServices } from '@app/services/services-api.service'; import { md5 } from '@app/shared/common.utils'; import { StateService } from '@app/services/state.service'; @@ -78,6 +78,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { @Output() changeMode = new EventEmitter(); @Output() paymentReceipt = new EventEmitter(); + private destroy$ = new Subject(); calculating = true; processing = false; isCheckoutLocked = 0; // reference counter, 0 = unlocked, >0 = locked @@ -105,6 +106,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { difficultySubscription: Subscription; estimateSubscription: Subscription; estimate: AccelerationEstimate; + estimate$: ReplaySubject = new ReplaySubject(1); maxBidBoost: number; // sats cost: number; // sats etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>; @@ -208,6 +210,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { if (this.themeStateSubscription) { this.themeStateSubscription.unsubscribe(); } + this.destroy$.next(); + this.destroy$.complete(); } ngOnChanges(changes: SimpleChanges): void { @@ -276,6 +280,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { closeModal(): void { this.completed.emit(true); this.moveToStep('summary', true); + this.accelerateError = ''; } /** @@ -298,6 +303,19 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } } + private estimateReady$(): Observable { + return this.estimate$.pipe( + filter((estimate: AccelerationEstimate | null): estimate is AccelerationEstimate => estimate !== null), + take(1), + takeUntil(this.destroy$), + timeout({ first: 30_000 }), + catchError(() => { + this.accelerateError = 'estimate_timeout'; + return of(); + }) + ); + } + /** * Accelerator */ @@ -322,6 +340,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { if (this.step === 'summary') { this.unavailable.emit(true); } + this.estimate$.next(null); return; } if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { @@ -344,6 +363,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.defaultBid = this.maxRateOptions[1].fee; this.userBid = this.defaultBid; this.cost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; + this.estimate$.next(this.estimate); this.validateChoice(); @@ -506,6 +526,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = true; + this.estimateReady$().subscribe(async () => { + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } + if (this.applePay) { this.applePay.destroy(); } @@ -611,6 +637,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = false; console.error(e); } + + }); } /** @@ -623,6 +651,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = true; + this.estimateReady$().subscribe(async () => { + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } + if (this.googlePay) { this.googlePay.destroy(); } @@ -731,6 +765,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.isCheckoutLocked--; } }); + + }); } /** @@ -743,6 +779,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = true; + this.estimateReady$().subscribe(async () => { + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } + const costUSD = this.cost / 100_000_000 * this.conversions.USD; if (this.isCheckoutLocked > 0) { return; @@ -829,6 +871,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.isCheckoutLocked--; this.isTokenizing--; } + + }); } /** @@ -841,6 +885,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = true; + this.estimateReady$().subscribe(async () => { + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } + if (this.cashAppPay) { this.cashAppPay.destroy(); } @@ -911,6 +961,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { }); } }); + + }); } /** From 849fe5e8861b54cb6620edb967b7123464528fed Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 27 Jun 2026 18:40:45 +0800 Subject: [PATCH 301/369] [misc] fix code indentation --- .../accelerate-checkout.component.ts | 630 +++++++++--------- 1 file changed, 313 insertions(+), 317 deletions(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 33539205b..f7b1b1f85 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -527,160 +527,37 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = true; this.estimateReady$().subscribe(async () => { - if (!this.estimate) { - // error state should already be handled by estimateReady - return; - } + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } - if (this.applePay) { - this.applePay.destroy(); - } + if (this.applePay) { + this.applePay.destroy(); + } - const costUSD = this.cost / 100_000_000 * this.conversions.USD; - const paymentRequest = this.payments.paymentRequest({ - countryCode: 'US', - currencyCode: 'USD', - total: { - amount: costUSD.toFixed(2), - label: 'Total', - }, - }); + const costUSD = this.cost / 100_000_000 * this.conversions.USD; + const paymentRequest = this.payments.paymentRequest({ + countryCode: 'US', + currencyCode: 'USD', + total: { + amount: costUSD.toFixed(2), + label: 'Total', + }, + }); - try { - this.applePay = await this.payments.applePay(paymentRequest); - const applePayButton = document.getElementById('apple-pay-button'); - if (!applePayButton) { - console.error(`Unable to find apple pay button id='apple-pay-button'`); - // Try again - setTimeout(this.requestApplePayPayment.bind(this), 500); - this.processing = false; - return; - } - this.loadingApplePay = false; - applePayButton.addEventListener('click', async event => { - if (this.isCheckoutLocked > 0 || this.isTokenizing > 0) { - return; - } - event.preventDefault(); - try { - // lock the checkout UI and show a loading spinner until the square modals are finished - this.isCheckoutLocked++; - this.isTokenizing++; - const tokenResult = await this.applePay.tokenize(); - if (tokenResult?.status === 'OK') { - const card = tokenResult.details?.card; - if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { - console.error(`Cannot retrieve payment card details`); - this.accelerateError = 'apple_pay_no_card_details'; - this.processing = false; - return; - } - const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); - // keep checkout in loading state until the acceleration request completes - this.isTokenizing++; - this.isCheckoutLocked++; - this.servicesApiService.accelerateWithApplePay$( - this.tx.txid, - tokenResult.token, - cardTag, - `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, - costUSD, - this.partnerCode - ).subscribe({ - next: (response) => { - this.partnerCodeService.clearStoredPartnerCode(); - this.accelerationResponse = response; - this.processing = false; - this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); - this.audioService.playSound('ascend-chime-cartoon'); - if (this.applePay) { - this.applePay.destroy(); - } - this.paymentReceipt.emit(this.accelerationResponse?.receiptUrl); - setTimeout(() => { - this.isTokenizing--; - this.isCheckoutLocked--; - this.moveToStep('paid', true); - }, 1000); - }, - error: (response) => { - this.processing = false; - this.accelerateError = response.error; - if (!(response.status === 403 && response.error === 'not_available')) { - setTimeout(() => { - this.isTokenizing--; - this.isCheckoutLocked--; - // Reset everything by reloading the page :D, can be improved - const urlParams = new URLSearchParams(window.location.search); - window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 10000); - } - } - }); - } else { - this.processing = false; - let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; - if (tokenResult.errors) { - errorMessage += ` and errors: ${JSON.stringify( - tokenResult.errors, - )}`; - } - throw new Error(errorMessage); - } - } finally { - // always unlock the checkout once we're finished - this.isTokenizing--; - this.isCheckoutLocked--; - } - }); - } catch (e) { + try { + this.applePay = await this.payments.applePay(paymentRequest); + const applePayButton = document.getElementById('apple-pay-button'); + if (!applePayButton) { + console.error(`Unable to find apple pay button id='apple-pay-button'`); + // Try again + setTimeout(this.requestApplePayPayment.bind(this), 500); this.processing = false; - console.error(e); - } - - }); - } - - /** - * GOOGLE PAY - */ - async requestGooglePayPayment(): Promise { - if (this.processing) { - return; - } - - this.processing = true; - - this.estimateReady$().subscribe(async () => { - if (!this.estimate) { - // error state should already be handled by estimateReady return; } - - if (this.googlePay) { - this.googlePay.destroy(); - } - - const costUSD = this.cost / 100_000_000 * this.conversions.USD; - const paymentRequest = this.payments.paymentRequest({ - countryCode: 'US', - currencyCode: 'USD', - total: { - amount: costUSD.toFixed(2), - label: 'Total' - } - }); - this.googlePay = await this.payments.googlePay(paymentRequest , { - referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, - }); - - await this.googlePay.attach(`#google-pay-button`, { - buttonType: 'pay', - buttonSizeMode: 'fill', - }); - this.loadingGooglePay = false; - - document.getElementById('google-pay-button').addEventListener('click', async event => { + this.loadingApplePay = false; + applePayButton.addEventListener('click', async event => { if (this.isCheckoutLocked > 0 || this.isTokenizing > 0) { return; } @@ -689,7 +566,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { // lock the checkout UI and show a loading spinner until the square modals are finished this.isCheckoutLocked++; this.isTokenizing++; - const tokenResult = await this.googlePay.tokenize(); + const tokenResult = await this.applePay.tokenize(); if (tokenResult?.status === 'OK') { const card = tokenResult.details?.card; if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { @@ -698,25 +575,16 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = false; return; } - const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2)); - if (!verificationToken || !verificationToken.token) { - console.error(`SCA verification failed`); - this.accelerateError = 'SCA Verification Failed. Payment Declined.'; - this.processing = false; - return; - } const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); // keep checkout in loading state until the acceleration request completes - this.isCheckoutLocked++; this.isTokenizing++; - this.servicesApiService.accelerateWithGooglePay$( + this.isCheckoutLocked++; + this.servicesApiService.accelerateWithApplePay$( this.tx.txid, tokenResult.token, - verificationToken.token, cardTag, `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, costUSD, - verificationToken.userChallenged, this.partnerCode ).subscribe({ next: (response) => { @@ -725,8 +593,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = false; this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); this.audioService.playSound('ascend-chime-cartoon'); - if (this.googlePay) { - this.googlePay.destroy(); + if (this.applePay) { + this.applePay.destroy(); } this.paymentReceipt.emit(this.accelerationResponse?.receiptUrl); setTimeout(() => { @@ -738,10 +606,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy { error: (response) => { this.processing = false; this.accelerateError = response.error; - this.isTokenizing--; - this.isCheckoutLocked--; if (!(response.status === 403 && response.error === 'not_available')) { setTimeout(() => { + this.isTokenizing--; + this.isCheckoutLocked--; // Reset everything by reloading the page :D, can be improved const urlParams = new URLSearchParams(window.location.search); window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); @@ -765,7 +633,137 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.isCheckoutLocked--; } }); + } catch (e) { + this.processing = false; + console.error(e); + } + }); + } + /** + * GOOGLE PAY + */ + async requestGooglePayPayment(): Promise { + if (this.processing) { + return; + } + + this.processing = true; + + this.estimateReady$().subscribe(async () => { + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } + + if (this.googlePay) { + this.googlePay.destroy(); + } + + const costUSD = this.cost / 100_000_000 * this.conversions.USD; + const paymentRequest = this.payments.paymentRequest({ + countryCode: 'US', + currencyCode: 'USD', + total: { + amount: costUSD.toFixed(2), + label: 'Total' + } + }); + this.googlePay = await this.payments.googlePay(paymentRequest , { + referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, + }); + + await this.googlePay.attach(`#google-pay-button`, { + buttonType: 'pay', + buttonSizeMode: 'fill', + }); + this.loadingGooglePay = false; + + document.getElementById('google-pay-button').addEventListener('click', async event => { + if (this.isCheckoutLocked > 0 || this.isTokenizing > 0) { + return; + } + event.preventDefault(); + try { + // lock the checkout UI and show a loading spinner until the square modals are finished + this.isCheckoutLocked++; + this.isTokenizing++; + const tokenResult = await this.googlePay.tokenize(); + if (tokenResult?.status === 'OK') { + const card = tokenResult.details?.card; + if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) { + console.error(`Cannot retrieve payment card details`); + this.accelerateError = 'apple_pay_no_card_details'; + this.processing = false; + return; + } + const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2)); + if (!verificationToken || !verificationToken.token) { + console.error(`SCA verification failed`); + this.accelerateError = 'SCA Verification Failed. Payment Declined.'; + this.processing = false; + return; + } + const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase()); + // keep checkout in loading state until the acceleration request completes + this.isCheckoutLocked++; + this.isTokenizing++; + this.servicesApiService.accelerateWithGooglePay$( + this.tx.txid, + tokenResult.token, + verificationToken.token, + cardTag, + `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, + costUSD, + verificationToken.userChallenged, + this.partnerCode + ).subscribe({ + next: (response) => { + this.partnerCodeService.clearStoredPartnerCode(); + this.accelerationResponse = response; + this.processing = false; + this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); + this.audioService.playSound('ascend-chime-cartoon'); + if (this.googlePay) { + this.googlePay.destroy(); + } + this.paymentReceipt.emit(this.accelerationResponse?.receiptUrl); + setTimeout(() => { + this.isTokenizing--; + this.isCheckoutLocked--; + this.moveToStep('paid', true); + }, 1000); + }, + error: (response) => { + this.processing = false; + this.accelerateError = response.error; + this.isTokenizing--; + this.isCheckoutLocked--; + if (!(response.status === 403 && response.error === 'not_available')) { + setTimeout(() => { + // Reset everything by reloading the page :D, can be improved + const urlParams = new URLSearchParams(window.location.search); + window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); + }, 10000); + } + } + }); + } else { + this.processing = false; + let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; + if (tokenResult.errors) { + errorMessage += ` and errors: ${JSON.stringify( + tokenResult.errors, + )}`; + } + throw new Error(errorMessage); + } + } finally { + // always unlock the checkout once we're finished + this.isTokenizing--; + this.isCheckoutLocked--; + } + }); }); } @@ -780,98 +778,97 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = true; this.estimateReady$().subscribe(async () => { - if (!this.estimate) { - // error state should already be handled by estimateReady - return; - } + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } - const costUSD = this.cost / 100_000_000 * this.conversions.USD; - if (this.isCheckoutLocked > 0) { - return; - } - const cardOnFile = this.estimate?.availablePaymentMethods?.cardOnFile; - if (!cardOnFile?.card) { - this.accelerateError = 'card_on_file_not_found'; - return; - } - this.loadingCardOnFile = false; + const costUSD = this.cost / 100_000_000 * this.conversions.USD; + if (this.isCheckoutLocked > 0) { + return; + } + const cardOnFile = this.estimate?.availablePaymentMethods?.cardOnFile; + if (!cardOnFile?.card) { + this.accelerateError = 'card_on_file_not_found'; + return; + } + this.loadingCardOnFile = false; - try { - this.isCheckoutLocked += 2; - this.isTokenizing += 2; + try { + this.isCheckoutLocked += 2; + this.isTokenizing += 2; - const nameParts = cardOnFile.card.name.split(' '); - const assumedGivenName = nameParts[0]; - const assumedFamilyName = nameParts.length > 1 ? nameParts[1] : undefined; - const verificationDetails = { - card: { - billing: { - givenName: assumedGivenName, - familyName: assumedFamilyName, - addressLines: [cardOnFile.card.billing.addressLine1 ?? ''], - city: cardOnFile.card.billing.locality ?? '', - state: cardOnFile.card.billing.administrativeDistrictLevel1 ?? '', - countyCode: cardOnFile.card.billing.country, - } + const nameParts = cardOnFile.card.name.split(' '); + const assumedGivenName = nameParts[0]; + const assumedFamilyName = nameParts.length > 1 ? nameParts[1] : undefined; + const verificationDetails = { + card: { + billing: { + givenName: assumedGivenName, + familyName: assumedFamilyName, + addressLines: [cardOnFile.card.billing.addressLine1 ?? ''], + city: cardOnFile.card.billing.locality ?? '', + state: cardOnFile.card.billing.administrativeDistrictLevel1 ?? '', + countyCode: cardOnFile.card.billing.country, } - }; - const verificationToken = await this.$verifyBuyer(this.payments, cardOnFile.card.card_id, verificationDetails, costUSD.toFixed(2)); - if (!verificationToken || !verificationToken.token) { - console.error(`SCA verification failed`); - this.accelerateError = 'SCA Verification Failed. Payment Declined.'; - this.processing = false; - return; } + }; + const verificationToken = await this.$verifyBuyer(this.payments, cardOnFile.card.card_id, verificationDetails, costUSD.toFixed(2)); + if (!verificationToken || !verificationToken.token) { + console.error(`SCA verification failed`); + this.accelerateError = 'SCA Verification Failed. Payment Declined.'; + this.processing = false; + return; + } - this.servicesApiService.accelerateWithCardOnFile$( - this.tx.txid, - cardOnFile.card.card_id, - verificationToken.token, - costUSD, - verificationToken.userChallenged, - this.partnerCode - ).subscribe({ - next: (response) => { - this.partnerCodeService.clearStoredPartnerCode(); - this.accelerationResponse = response; - this.processing = false; - this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); - this.audioService.playSound('ascend-chime-cartoon'); - this.paymentReceipt.emit(this.accelerationResponse?.receiptUrl); - setTimeout(() => { - this.isCheckoutLocked--; - this.isTokenizing--; - this.moveToStep('paid', true); - }, 1000); - }, - error: (response) => { - this.processing = false; - this.accelerateError = response.error; + this.servicesApiService.accelerateWithCardOnFile$( + this.tx.txid, + cardOnFile.card.card_id, + verificationToken.token, + costUSD, + verificationToken.userChallenged, + this.partnerCode + ).subscribe({ + next: (response) => { + this.partnerCodeService.clearStoredPartnerCode(); + this.accelerationResponse = response; + this.processing = false; + this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); + this.audioService.playSound('ascend-chime-cartoon'); + this.paymentReceipt.emit(this.accelerationResponse?.receiptUrl); + setTimeout(() => { this.isCheckoutLocked--; this.isTokenizing--; - if (!(response.status === 403 && response.error === 'not_available')) { - setTimeout(() => { - // Reset everything by reloading the page :D, can be improved - const urlParams = new URLSearchParams(window.location.search); - window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 3000); - } + this.moveToStep('paid', true); + }, 1000); + }, + error: (response) => { + this.processing = false; + this.accelerateError = response.error; + this.isCheckoutLocked--; + this.isTokenizing--; + if (!(response.status === 403 && response.error === 'not_available')) { + setTimeout(() => { + // Reset everything by reloading the page :D, can be improved + const urlParams = new URLSearchParams(window.location.search); + window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); + }, 3000); } - }); + } + }); - } catch (e) { - console.log(e); - this.isCheckoutLocked--; - this.isTokenizing--; - this.processing = false; - this.accelerateError = e.message; - - } finally { - // always unlock the checkout once we're finished - this.isCheckoutLocked--; - this.isTokenizing--; - } + } catch (e) { + console.log(e); + this.isCheckoutLocked--; + this.isTokenizing--; + this.processing = false; + this.accelerateError = e.message; + } finally { + // always unlock the checkout once we're finished + this.isCheckoutLocked--; + this.isTokenizing--; + } }); } @@ -886,82 +883,81 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.processing = true; this.estimateReady$().subscribe(async () => { - if (!this.estimate) { - // error state should already be handled by estimateReady - return; + if (!this.estimate) { + // error state should already be handled by estimateReady + return; + } + + if (this.cashAppPay) { + this.cashAppPay.destroy(); + } + + const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`; + const costUSD = this.cost / 100_000_000 * this.conversions.USD; + const paymentRequest = this.payments.paymentRequest({ + countryCode: 'US', + currencyCode: 'USD', + total: { + amount: costUSD.toFixed(2), + label: 'Total', + pending: true, + productUrl: `${redirectHostname}/tx/${this.tx.txid}`, } + }); + this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { + redirectURL: `${redirectHostname}/tx/${this.tx.txid}`, + referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}` + }); - if (this.cashAppPay) { - this.cashAppPay.destroy(); - } + await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'dark' }); + this.loadingCashapp = false; - const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`; - const costUSD = this.cost / 100_000_000 * this.conversions.USD; - const paymentRequest = this.payments.paymentRequest({ - countryCode: 'US', - currencyCode: 'USD', - total: { - amount: costUSD.toFixed(2), - label: 'Total', - pending: true, - productUrl: `${redirectHostname}/tx/${this.tx.txid}`, - } - }); - this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { - redirectURL: `${redirectHostname}/tx/${this.tx.txid}`, - referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}` - }); - - await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'dark' }); - this.loadingCashapp = false; - - this.cashAppPay.addEventListener('ontokenization', event => { - const { tokenResult, error } = event.detail; - if (error) { - this.processing = false; - this.accelerateError = error; - } else if (tokenResult.status === 'OK') { - this.servicesApiService.accelerateWithCashApp$( - this.tx.txid, - tokenResult.token, - tokenResult.details.cashAppPay.cashtag, - tokenResult.details.cashAppPay.referenceId, - costUSD, - this.partnerCode - ).subscribe({ - next: (response) => { - this.partnerCodeService.clearStoredPartnerCode(); - this.accelerationResponse = response; - this.processing = false; - this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); - this.audioService.playSound('ascend-chime-cartoon'); - if (this.cashAppPay) { - this.cashAppPay.destroy(); - } - this.paymentReceipt.emit(this.accelerationResponse?.receiptUrl); - setTimeout(() => { - this.moveToStep('paid', true); - if (window.history.replaceState) { - const urlParams = new URLSearchParams(window.location.search); - window.history.replaceState(null, null, window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, '')); - } - }, 1000); - }, - error: (response) => { - this.processing = false; - this.accelerateError = response.error; - if (!(response.status === 403 && response.error === 'not_available')) { - setTimeout(() => { - // Reset everything by reloading the page :D, can be improved - const urlParams = new URLSearchParams(window.location.search); - window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); - }, 10000); - } + this.cashAppPay.addEventListener('ontokenization', event => { + const { tokenResult, error } = event.detail; + if (error) { + this.processing = false; + this.accelerateError = error; + } else if (tokenResult.status === 'OK') { + this.servicesApiService.accelerateWithCashApp$( + this.tx.txid, + tokenResult.token, + tokenResult.details.cashAppPay.cashtag, + tokenResult.details.cashAppPay.referenceId, + costUSD, + this.partnerCode + ).subscribe({ + next: (response) => { + this.partnerCodeService.clearStoredPartnerCode(); + this.accelerationResponse = response; + this.processing = false; + this.apiService.logAccelerationRequest$(this.tx.txid).subscribe(); + this.audioService.playSound('ascend-chime-cartoon'); + if (this.cashAppPay) { + this.cashAppPay.destroy(); } - }); - } - }); - + this.paymentReceipt.emit(this.accelerationResponse?.receiptUrl); + setTimeout(() => { + this.moveToStep('paid', true); + if (window.history.replaceState) { + const urlParams = new URLSearchParams(window.location.search); + window.history.replaceState(null, null, window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, '')); + } + }, 1000); + }, + error: (response) => { + this.processing = false; + this.accelerateError = response.error; + if (!(response.status === 403 && response.error === 'not_available')) { + setTimeout(() => { + // Reset everything by reloading the page :D, can be improved + const urlParams = new URLSearchParams(window.location.search); + window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``)); + }, 10000); + } + } + }); + } + }); }); } From f802817adc6fd0f7e1343f201bd7ccdb2afd4926 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 29 Jun 2026 04:56:20 +0000 Subject: [PATCH 302/369] only show accelerator savings msg on mainnet --- .../transaction-details/transaction-details.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts index 4b3c7806e..9a1b0e2a2 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts @@ -92,6 +92,10 @@ export class TransactionDetailsComponent implements OnChanges { } calculateAcceleratorSavings(block: BlockExtended): void { + if (this.network !== '') { + this.acceleratorSavingsSats = 0; + return; + } const minBlockRate = block?.extras?.feeRange?.[0]; if (minBlockRate !== undefined) { const vsize = this.tx.weight / 4; @@ -100,7 +104,7 @@ export class TransactionDetailsComponent implements OnChanges { } get showAcceleratorSavingsMsg(): boolean { - return this.acceleratorSavingsSats > 0 && this.cpfpInfo !== null && this.cpfpInfo !== undefined && !this.hasCpfp; + return this.network === '' && this.acceleratorSavingsSats > 0 && this.cpfpInfo !== null && this.cpfpInfo !== undefined && !this.hasCpfp; } onAccelerateClicked(): void { From b978b8ae4c335d3193a1644b8a11ceecb377639f Mon Sep 17 00:00:00 2001 From: natsoni Date: Mon, 29 Jun 2026 11:39:44 +0200 Subject: [PATCH 303/369] Send stratum jobs only when requested on stratum page --- backend/src/api/websocket-handler.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index cea0ce781..90f85a383 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -450,7 +450,7 @@ class WebsocketHandler { if (parsedMessage['track-stratum'] === 'all' || typeof parsedMessage['track-stratum'] === 'number') { const sub = parsedMessage['track-stratum']; client['track-stratum'] = sub; - response['stratumJobs'] = this.socketData['stratumJobs']; + response['stratumJobs'] = JSON.stringify(stratumApi.getJobs()); } else { client['track-stratum'] = false; } @@ -1369,8 +1369,6 @@ class WebsocketHandler { } public handleNewStratumJob(job: StratumJob): void { - this.updateSocketDataFields({ 'stratumJobs': stratumApi.getJobs() }); - for (const server of this.webSocketServers) { server.clients.forEach((client) => { if (client.readyState !== WebSocket.OPEN) { From 4e16f30492bb5c7ddeb285fd431906b7d65008b6 Mon Sep 17 00:00:00 2001 From: mononaut Date: Tue, 30 Jun 2026 03:29:21 +0000 Subject: [PATCH 304/369] fix failing liquid asset e2e tests --- frontend/cypress/e2e/liquid/liquid.spec.ts | 2 +- frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/cypress/e2e/liquid/liquid.spec.ts b/frontend/cypress/e2e/liquid/liquid.spec.ts index 20f983844..6d58bf5e8 100644 --- a/frontend/cypress/e2e/liquid/liquid.spec.ts +++ b/frontend/cypress/e2e/liquid/liquid.spec.ts @@ -113,7 +113,7 @@ describe('Liquid', () => { it('allows searching assets', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); - cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { + cy.get('.container-xl input').click().type('Tether USD').then(() => { cy.get('ngb-typeahead-window', { timeout: 30000 }).should('have.length', 1); }); }); diff --git a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts index aef6dc51d..aa3ee6787 100644 --- a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts +++ b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts @@ -79,7 +79,7 @@ describe('Liquid Testnet', () => { it('allows searching assets', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); - cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { + cy.get('.container-xl input').click().type('Tether USD').then(() => { cy.get('ngb-typeahead-window').should('have.length', 1); }); }); From 3b1db74761e5db37d54625816ba139e0c48f693b Mon Sep 17 00:00:00 2001 From: mononaut Date: Tue, 30 Jun 2026 07:54:52 +0000 Subject: [PATCH 305/369] fix stratum websocket un-subscriptions --- backend/src/api/websocket-handler.ts | 2 +- frontend/src/app/components/pool/pool.component.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 90f85a383..17bc72890 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -446,7 +446,7 @@ class WebsocketHandler { delete client['track-mempool']; } - if (parsedMessage && parsedMessage['track-stratum'] != null) { + if (parsedMessage && parsedMessage['track-stratum'] !== undefined) { if (parsedMessage['track-stratum'] === 'all' || typeof parsedMessage['track-stratum'] === 'number') { const sub = parsedMessage['track-stratum']; client['track-stratum'] = sub; diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 1b006549c..427035693 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { echarts, EChartsOption } from '@app/graphs/echarts'; import { BehaviorSubject, Observable, Subscription, combineLatest, of } from 'rxjs'; @@ -26,7 +26,7 @@ interface AccelerationTotal { standalone: false, changeDetection: ChangeDetectionStrategy.OnPush }) -export class PoolComponent implements OnInit { +export class PoolComponent implements OnInit, OnDestroy { @Input() right: number | string = 45; @Input() left: number | string = 75; @@ -365,5 +365,6 @@ export class PoolComponent implements OnInit { ngOnDestroy(): void { this.slugSubscription.unsubscribe(); + this.websocketService.stopTrackStratum(); } } From ca727d286990c3a081bb47f41062113e49cc2cd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 19:45:37 +0000 Subject: [PATCH 306/369] Bump echarts from 5.4.3 to 6.1.0 in /frontend Bumps [echarts](https://github.com/apache/echarts) from 5.4.3 to 6.1.0. - [Release notes](https://github.com/apache/echarts/releases) - [Commits](https://github.com/apache/echarts/compare/5.4.3...6.1.0) --- updated-dependencies: - dependency-name: echarts dependency-version: 6.1.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 270 ++++++++++++++++++++++++++++++++++--- frontend/package.json | 2 +- 2 files changed, 252 insertions(+), 20 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 57406f287..380138485 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,7 +32,7 @@ "bootstrap": "~5.3.8", "clipboard": "^2.0.11", "domino": "^2.1.6", - "echarts": "~5.4.0", + "echarts": "~6.1.0", "ngx-echarts": "~20.0.2", "ngx-infinite-scroll": "^20.0.0", "qrcode": "1.5.1", @@ -354,6 +354,22 @@ } } }, + "node_modules/@angular-devkit/architect/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/architect/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -372,6 +388,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/architect/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/architect/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -989,6 +1019,22 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1107,6 +1153,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -1243,6 +1303,22 @@ } } }, + "node_modules/@angular-devkit/schematics/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1261,6 +1337,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/schematics/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/schematics/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -2145,6 +2235,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@angular/cli/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/cli/node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -2220,6 +2326,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular/cli/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/cli/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -7455,6 +7575,22 @@ } } }, + "node_modules/@schematics/angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@schematics/angular/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7473,6 +7609,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@schematics/angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@schematics/angular/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -10671,13 +10821,12 @@ } }, "node_modules/echarts": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", - "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", - "license": "Apache-2.0", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "dependencies": { "tslib": "2.3.0", - "zrender": "5.4.4" + "zrender": "6.1.0" } }, "node_modules/echarts/node_modules/tslib": { @@ -18629,10 +18778,9 @@ "license": "MIT" }, "node_modules/zrender": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", - "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", - "license": "BSD-3-Clause", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", "dependencies": { "tslib": "2.3.0" } @@ -18640,8 +18788,7 @@ "node_modules/zrender/node_modules/tslib": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } }, "dependencies": { @@ -18845,6 +18992,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -18855,6 +19012,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19112,6 +19276,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -19192,6 +19366,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -19268,6 +19449,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -19278,6 +19469,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19687,6 +19885,16 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -19734,6 +19942,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -22612,6 +22827,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -22622,6 +22847,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -24919,12 +25151,12 @@ } }, "echarts": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", - "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "requires": { "tslib": "2.3.0", - "zrender": "5.4.4" + "zrender": "6.1.0" }, "dependencies": { "tslib": { @@ -30309,9 +30541,9 @@ "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==" }, "zrender": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", - "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", "requires": { "tslib": "2.3.0" }, diff --git a/frontend/package.json b/frontend/package.json index 829694835..9fe8f89ef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -82,7 +82,7 @@ "bootstrap": "~5.3.8", "clipboard": "^2.0.11", "domino": "^2.1.6", - "echarts": "~5.4.0", + "echarts": "~6.1.0", "ngx-echarts": "~20.0.2", "ngx-infinite-scroll": "^20.0.0", "qrcode": "1.5.1", From 365eab002384f0bf2995b71eed059486954be45a Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 1 Jul 2026 10:38:53 +0900 Subject: [PATCH 307/369] [misc] fix log typo --- backend/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 3b2296091..bb93ebff8 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -164,7 +164,7 @@ class Server { await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it if (config.DATABASE.ENABLED === true && config.MEMPOOL.ENABLED && ['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK) && !poolsUpdater.currentSha) { - logger.err(`Failed to retreive pools-v2.json sha, cannot run block indexing. Please make sure you've set valid urls in your mempool-config.json::MEMPOOL::POOLS_JSON_URL and mempool-config.json::MEMPOOL::POOLS_JSON_TREE_UR, aborting now`); + logger.err(`Failed to retrieve pools-v2.json sha, cannot run block indexing. Please make sure you've set valid urls in your mempool-config.json::MEMPOOL::POOLS_JSON_URL and mempool-config.json::MEMPOOL::POOLS_JSON_TREE_UR, aborting now`); return process.exit(1); } From 8a9904dbe694c17d353a6f40bf7d1f1def59c70b Mon Sep 17 00:00:00 2001 From: wiz Date: Wed, 1 Jul 2026 15:09:30 +0900 Subject: [PATCH 308/369] ops: Enable -DWITH_ZMQ=ON when building Bitcoin Core --- production/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/install b/production/install index d97fd39d7..dcce38e1c 100755 --- a/production/install +++ b/production/install @@ -1162,7 +1162,7 @@ if [ "${BITCOIN_INSTALL}" = ON ];then # use cmake if > v28 if [ "${BITCOIN_LATEST_RELEASE:1:2}" -gt "28" ];then - osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_REPO_NAME} && cmake -B build -DWITH_GUI=OFF -DENABLE_IPC=OFF -DENABLE_WALLET=OFF -DBUILD_BITCOIN_TESTS=OFF" + osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_REPO_NAME} && cmake -B build -DWITH_GUI=OFF -DWITH_ZMQ=ON -DENABLE_IPC=OFF -DENABLE_WALLET=OFF -DBUILD_BITCOIN_TESTS=OFF" osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_REPO_NAME} && cmake --build build -j${NPROC}" echo "[*] Installing Bitcoin binaries into OS" From 3b192f6ff5f2f7458dc98f2c876be119d13230de Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Thu, 2 Jul 2026 02:04:41 +0900 Subject: [PATCH 309/369] Add user agent and log urls on the sync-assets script --- frontend/sync-assets.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index 447557a51..f0797b563 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -80,9 +80,9 @@ const downloadFile = (filePath, url) => { } return new Promise((resolve, reject) => { - https.get(url, (response) => { + https.get(url, { headers: { 'user-agent': 'node.js' } }, (response) => { if (response.statusCode < 200 || response.statusCode > 299) { - reject(new Error(`HTTP Error ${response.statusCode} while fetching '${filePath}'`)); + reject(new Error(`HTTP Error ${response.statusCode} while fetching '${url}' (destination '${filePath}')`)); return; } From a595bb44f9f0e6afd15bb039651cd863e13fb916 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 3 Jul 2026 17:51:18 +0900 Subject: [PATCH 310/369] [ssr] accept 301 and 302 as valid for nginx cache --- production/nginx/server-common.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/nginx/server-common.conf b/production/nginx/server-common.conf index 5532076ec..27cde5d19 100644 --- a/production/nginx/server-common.conf +++ b/production/nginx/server-common.conf @@ -217,7 +217,7 @@ location @mempool-space-ssr { proxy_cache_background_update on; proxy_cache_use_stale updating; proxy_cache unfurler; - proxy_cache_valid 200 5m; # will re-render page if older than this + proxy_cache_valid 200 301 302 5m; # will re-render page if older than this proxy_redirect off; expires 5m; From 574048eed5ff3e0f56ff4421675167000db8ce1f Mon Sep 17 00:00:00 2001 From: jramos0 Date: Fri, 3 Jul 2026 03:47:32 -0600 Subject: [PATCH 311/369] Fix update accelerator dashboard stats in real time #6601 --- .../acceleration-stats.component.ts | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts index fad1bc7b9..ddfc5351e 100644 --- a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable, merge, catchError, debounceTime, distinctUntilChanged, filter, switchMap, tap } from 'rxjs'; import { ServicesApiServices } from '@app/services/services-api.service'; +import { StateService } from '@app/services/state.service'; export type AccelerationStats = { totalRequested: number; @@ -21,21 +22,44 @@ export class AccelerationStatsComponent implements OnInit, OnChanges { @Input() timespan: '24h' | '1m' | '1y' | 'all' = '1y'; accelerationStats$: Observable; blocksInPeriod: number = 7 * 144; + private timespan$ = new BehaviorSubject<'24h' | '1m' | '1y' | 'all'>( + this.timespan + ); constructor( - private servicesApiService: ServicesApiServices - ) { } + private servicesApiService: ServicesApiServices, + private stateService: StateService + ) {} ngOnInit(): void { - this.updateStats(); + const websocketRefresh$ = this.stateService.accelerations$.pipe( + filter( + (delta) => + !delta.reset && (!!delta.added.length || !!delta.removed.length) + ), + debounceTime(2000) + ); + + this.accelerationStats$ = merge( + this.timespan$.pipe(tap(() => this.updateBlocksInPeriod())), + websocketRefresh$, + this.stateService.chainTip$.pipe(distinctUntilChanged()) + ).pipe( + switchMap(() => + this.servicesApiService + .getAccelerationStats$({ timeframe: this.timespan }) + .pipe( + catchError(() => EMPTY) + ) + ) + ); } ngOnChanges(): void { - this.updateStats(); + this.timespan$.next(this.timespan); } - updateStats(): void { - this.accelerationStats$ = this.servicesApiService.getAccelerationStats$({ timeframe: this.timespan }); + private updateBlocksInPeriod(): void { switch (this.timespan) { case '24h': this.blocksInPeriod = 144; From 457b0e6ffae66a8d74ffe75471cef7dc95fb5145 Mon Sep 17 00:00:00 2001 From: jramos0 Date: Fri, 3 Jul 2026 04:18:15 -0600 Subject: [PATCH 312/369] Adding debounceTime --- .../acceleration-stats/acceleration-stats.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts index ddfc5351e..9735055c4 100644 --- a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts @@ -45,6 +45,7 @@ export class AccelerationStatsComponent implements OnInit, OnChanges { websocketRefresh$, this.stateService.chainTip$.pipe(distinctUntilChanged()) ).pipe( + debounceTime(0), switchMap(() => this.servicesApiService .getAccelerationStats$({ timeframe: this.timespan }) From 0265ffa9ac797addf61ec1940849f49313203750 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 3 Jul 2026 19:20:09 +0900 Subject: [PATCH 313/369] [ssr] accept 404 as valid status for static images caching --- production/nginx/server-common.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/production/nginx/server-common.conf b/production/nginx/server-common.conf index 27cde5d19..c5af76fc3 100644 --- a/production/nginx/server-common.conf +++ b/production/nginx/server-common.conf @@ -258,6 +258,7 @@ location @mempool-space-static { proxy_cache_use_stale updating; proxy_cache unfurler; proxy_cache_valid 200 5m; # will re-render page if older than this + proxy_cache_valid 404 30s; # will re-render page if older than this proxy_redirect off; expires 1d; From 5a44d53fd6d8a1f3d7918c44a5edead85b275de2 Mon Sep 17 00:00:00 2001 From: natsoni Date: Fri, 3 Jul 2026 12:26:24 +0200 Subject: [PATCH 314/369] Fix effective fee row on mobile --- .../transaction-details.component.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.scss b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.scss index 3623c4a31..a9e4435f4 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.scss +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.scss @@ -172,8 +172,16 @@ gap: 0.5rem; flex-wrap: wrap; + @media (max-width: 849.98px) { + justify-content: flex-end; + } + .effective-fee-container { flex: 0 0 auto; + + @media (max-width: 425px) { + align-items: flex-end; + } } } } From ec74c3472d2d87d38488bdad3b7f0d8614f182b9 Mon Sep 17 00:00:00 2001 From: jramos0 Date: Fri, 3 Jul 2026 04:49:47 -0600 Subject: [PATCH 315/369] Changing debounce to throttle --- .../acceleration-stats/acceleration-stats.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts index 9735055c4..7eb26ce23 100644 --- a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core'; -import { BehaviorSubject, EMPTY, Observable, merge, catchError, debounceTime, distinctUntilChanged, filter, switchMap, tap } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable, merge, catchError, debounceTime, distinctUntilChanged, filter, switchMap, tap, throttleTime } from 'rxjs'; import { ServicesApiServices } from '@app/services/services-api.service'; import { StateService } from '@app/services/state.service'; @@ -37,7 +37,7 @@ export class AccelerationStatsComponent implements OnInit, OnChanges { (delta) => !delta.reset && (!!delta.added.length || !!delta.removed.length) ), - debounceTime(2000) + throttleTime(2000) ); this.accelerationStats$ = merge( From b04cafeeb344e787363154f97087895633029d2e Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 3 Jul 2026 19:55:29 +0900 Subject: [PATCH 316/369] [enterprise] show default logo if unset by admin --- .../master-page/master-page.component.html | 4 ++-- frontend/src/resources/enterprise-big.png | Bin 0 -> 212189 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 frontend/src/resources/enterprise-big.png diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 8234b40be..ca6b26f26 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -22,7 +22,7 @@ @if (enterpriseInfo) {
- +
@if (enterpriseInfo.dualLogo) {
@@ -43,7 +43,7 @@ @if (enterpriseInfo) {
- +
@if (enterpriseInfo.dualLogo) {
diff --git a/frontend/src/resources/enterprise-big.png b/frontend/src/resources/enterprise-big.png new file mode 100644 index 0000000000000000000000000000000000000000..dfe233c8f0ba4ab13bea1f7f0643ed25131cc9bf GIT binary patch literal 212189 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%0DJklyFcvYZxGmmjAs!Z_ma%Wn*phx--`ax0X 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 literal 0 HcmV?d00001 From 59f9272446d6740a9d2e7a9ab63666e21ae58f81 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sat, 4 Jul 2026 13:05:19 +0900 Subject: [PATCH 317/369] Add nginx config validation workflow --- .github/nginx-check/Dockerfile.docker | 16 ++++++++++++ .github/nginx-check/Dockerfile.production | 13 +++++++++ .github/workflows/server-config.yml | 32 +++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 .github/nginx-check/Dockerfile.docker create mode 100644 .github/nginx-check/Dockerfile.production create mode 100644 .github/workflows/server-config.yml diff --git a/.github/nginx-check/Dockerfile.docker b/.github/nginx-check/Dockerfile.docker new file mode 100644 index 000000000..7dc6d2a5b --- /dev/null +++ b/.github/nginx-check/Dockerfile.docker @@ -0,0 +1,16 @@ +FROM nginx:1.30.1-alpine + +# Validate the config set loaded by the mempool/frontend docker image. +# Build context: repo root +COPY nginx.conf http-basic.conf /etc/nginx/ +COPY nginx-mempool.conf /etc/nginx/conf.d/ + +# Replay the transforms applied by docker/init.sh (with the default values +# docker/frontend/entrypoint.sh substitutes at container start) +RUN sed -i \ + -e "s!127.0.0.1:80!0.0.0.0:8080!g" \ + -e "s!user nobody;!!g" \ + -e "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" \ + /etc/nginx/nginx.conf + +CMD ["nginx", "-t"] diff --git a/.github/nginx-check/Dockerfile.production b/.github/nginx-check/Dockerfile.production new file mode 100644 index 000000000..cb91c2fc6 --- /dev/null +++ b/.github/nginx-check/Dockerfile.production @@ -0,0 +1,13 @@ +FROM nginx:1.30.1-alpine + +# Stage the config the way production/install does: the repo is available at +# ${NGINX_ETC_FOLDER}/mempool and production/nginx/nginx.conf becomes the main +# config, with __NGINX_USER__ and __NGINX_ETC_FOLDER__ substituted. +# Build context: production/ +COPY nginx /etc/nginx/mempool/production/nginx + +RUN cp /etc/nginx/mempool/production/nginx/nginx.conf /etc/nginx/nginx.conf && \ + sed -i "s!__NGINX_USER__!nginx!" /etc/nginx/nginx.conf && \ + sed -i "s!__NGINX_ETC_FOLDER__!/etc/nginx!" /etc/nginx/nginx.conf + +CMD ["nginx", "-t"] diff --git a/.github/workflows/server-config.yml b/.github/workflows/server-config.yml new file mode 100644 index 000000000..d1a89173c --- /dev/null +++ b/.github/workflows/server-config.yml @@ -0,0 +1,32 @@ +name: Validate server config + +on: + pull_request: + types: [opened, synchronize] + paths: + - "nginx.conf" + - "http-basic.conf" + - "nginx-mempool.conf" + - "production/nginx/**" + - ".github/nginx-check/**" + - ".github/workflows/server-config.yml" + +permissions: + contents: read + +jobs: + check_config: + runs-on: "ubuntu-latest" + + name: Validate nginx config + steps: + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + + - name: Validate the production nginx config + run: | + docker run --rm $(docker build -q -f .github/nginx-check/Dockerfile.production production) + + - name: Validate the docker frontend nginx config + run: | + docker run --rm $(docker build -q -f .github/nginx-check/Dockerfile.docker .) From 2fa37ff787853d20c43ae1506a90ad4e90572501 Mon Sep 17 00:00:00 2001 From: wiz Date: Sat, 4 Jul 2026 14:17:52 +0900 Subject: [PATCH 318/369] Add suggested fix from copilot review for nginx CI Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/nginx-check/Dockerfile.docker | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/nginx-check/Dockerfile.docker b/.github/nginx-check/Dockerfile.docker index 7dc6d2a5b..68776f088 100644 --- a/.github/nginx-check/Dockerfile.docker +++ b/.github/nginx-check/Dockerfile.docker @@ -9,6 +9,7 @@ COPY nginx-mempool.conf /etc/nginx/conf.d/ # docker/frontend/entrypoint.sh substitutes at container start) RUN sed -i \ -e "s!127.0.0.1:80!0.0.0.0:8080!g" \ + -e "s!127.0.0.1!0.0.0.0!g" \ -e "s!user nobody;!!g" \ -e "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" \ /etc/nginx/nginx.conf From 60bd6fa14d57162089baea2646480709cc162ef3 Mon Sep 17 00:00:00 2001 From: wiz Date: Sat, 4 Jul 2026 17:59:54 +0900 Subject: [PATCH 319/369] ops: Enable unix socket for mempool backends --- production/mempool-config.liquid.json | 1 + production/mempool-config.liquidtestnet.json | 1 + production/mempool-config.mainnet.json | 1 + production/mempool-config.signet.json | 1 + production/mempool-config.testnet.json | 1 + production/mempool-config.testnet4.json | 1 + 6 files changed, 6 insertions(+) diff --git a/production/mempool-config.liquid.json b/production/mempool-config.liquid.json index 316f99305..0a8f6384f 100644 --- a/production/mempool-config.liquid.json +++ b/production/mempool-config.liquid.json @@ -4,6 +4,7 @@ "NETWORK": "liquid", "BACKEND": "esplora", "HTTP_PORT": 8998, + "UNIX_SOCKET_PATH": "/mempool/socket/mempool-elements-liquid", "MINED_BLOCKS_CACHE": 144, "SPAWN_CLUSTER_PROCS": 0, "API_URL_PREFIX": "/api/v1/", diff --git a/production/mempool-config.liquidtestnet.json b/production/mempool-config.liquidtestnet.json index e42bbadd6..ad3e5ebbd 100644 --- a/production/mempool-config.liquidtestnet.json +++ b/production/mempool-config.liquidtestnet.json @@ -4,6 +4,7 @@ "NETWORK": "liquid", "BACKEND": "esplora", "HTTP_PORT": 8994, + "UNIX_SOCKET_PATH": "/mempool/socket/mempool-elements-liquidtestnet", "MINED_BLOCKS_CACHE": 144, "SPAWN_CLUSTER_PROCS": 0, "API_URL_PREFIX": "/api/v1/", diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 3a135f701..ee786891c 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -4,6 +4,7 @@ "NETWORK": "mainnet", "BACKEND": "esplora", "HTTP_PORT": 8999, + "UNIX_SOCKET_PATH": "/mempool/socket/mempool-bitcoin-mainnet", "CACHE_ENABLED": false, "MINED_BLOCKS_CACHE": 144, "SPAWN_CLUSTER_PROCS": 0, diff --git a/production/mempool-config.signet.json b/production/mempool-config.signet.json index 8155ced03..568745e80 100644 --- a/production/mempool-config.signet.json +++ b/production/mempool-config.signet.json @@ -4,6 +4,7 @@ "NETWORK": "signet", "BACKEND": "esplora", "HTTP_PORT": 8995, + "UNIX_SOCKET_PATH": "/mempool/socket/mempool-bitcoin-signet", "MINED_BLOCKS_CACHE": 144, "SPAWN_CLUSTER_PROCS": 0, "API_URL_PREFIX": "/api/v1/", diff --git a/production/mempool-config.testnet.json b/production/mempool-config.testnet.json index fdd5d78f7..033a35178 100644 --- a/production/mempool-config.testnet.json +++ b/production/mempool-config.testnet.json @@ -4,6 +4,7 @@ "NETWORK": "testnet", "BACKEND": "esplora", "HTTP_PORT": 8997, + "UNIX_SOCKET_PATH": "/mempool/socket/mempool-bitcoin-testnet", "MINED_BLOCKS_CACHE": 144, "SPAWN_CLUSTER_PROCS": 0, "API_URL_PREFIX": "/api/v1/", diff --git a/production/mempool-config.testnet4.json b/production/mempool-config.testnet4.json index 19489194c..ec47c7875 100644 --- a/production/mempool-config.testnet4.json +++ b/production/mempool-config.testnet4.json @@ -4,6 +4,7 @@ "NETWORK": "testnet", "BACKEND": "esplora", "HTTP_PORT": 8990, + "UNIX_SOCKET_PATH": "/mempool/socket/mempool-bitcoin-testnet4", "MINED_BLOCKS_CACHE": 144, "SPAWN_CLUSTER_PROCS": 0, "API_URL_PREFIX": "/api/v1/", From 6be3a641b2b32aaae161511b8a8ab6ceeda5b5f1 Mon Sep 17 00:00:00 2001 From: wiz Date: Sat, 4 Jul 2026 18:02:00 +0900 Subject: [PATCH 320/369] ops: Create /mempool/socket from prod install script --- production/install | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/production/install b/production/install index dcce38e1c..f2810c80c 100755 --- a/production/install +++ b/production/install @@ -550,6 +550,9 @@ zfsCreateFilesystems() zfs create -o "mountpoint=${ELEMENTS_HOME}/liquidv1" "${ZPOOL}/elements/liquidv1" zfs create -o "mountpoint=${ELEMENTS_ELECTRS_HOME}" "${ZPOOL}/elements/electrs" + # create /mempool/socket with custom ACL for electrs unix sockets + zfs create -o "mountpoint=${MEMPOOL_HOME}/socket" "${ZPOOL}/mempool/socket" + # create /bitcoin/socket with custom ACL for electrs unix sockets zfs create -o "mountpoint=${BITCOIN_HOME}/socket" "${ZPOOL}/bitcoin/socket" @@ -1838,6 +1841,8 @@ echo "[*] Setting permissions for electrs sockets" case $OS in FreeBSD) + setfacl -m "user:bitcoin:full_set:f:allow,user:mempool:full_set:f:allow,user:www:full_set:f:allow,everyone@::f:allow" "${MEMPOOL_HOME}/socket" + chown "${MEMPOOL_USER}:${MEMPOOL_GROUP}" "${MEMPOOL_HOME}/socket" setfacl -m "user:bitcoin:full_set:f:allow,user:mempool:full_set:f:allow,user:www:full_set:f:allow,everyone@::f:allow" "${BITCOIN_HOME}/socket" chown "${BITCOIN_USER}:${BITCOIN_GROUP}" "${BITCOIN_HOME}/socket" setfacl -m "user:elements:full_set:f:allow,user:mempool:full_set:f:allow,user:www:full_set:f:allow,everyone@::f:allow" "${ELEMENTS_HOME}/socket" From 235ffba805b297e9845e297ef176ccc2360438dd Mon Sep 17 00:00:00 2001 From: mononaut Date: Sat, 4 Jul 2026 09:10:20 +0000 Subject: [PATCH 321/369] [ops] log backend server socket counts --- backend/src/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/src/index.ts b/backend/src/index.ts index bb93ebff8..fe56486f1 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -400,6 +400,16 @@ class Server { this.warnedHeapCritical = false; this.maxHeapSize = 0; this.lastHeapLogTime = now; + this.server?.getConnections((error, count) => { + if (!error) { + logger.debug(`${count} open TCP sockets`); + } + }); + this.serverUnixSocket?.getConnections((error, count) => { + if (!error) { + logger.debug(`${count} open unix sockets`); + } + }); } } From c2462c34a58374f7c064802461a6bfd712cf91a8 Mon Sep 17 00:00:00 2001 From: mononaut Date: Sat, 4 Jul 2026 09:45:49 +0000 Subject: [PATCH 322/369] [ops] increase server timeouts --- backend/src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/index.ts b/backend/src/index.ts index fe56486f1..e857a377b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -230,6 +230,8 @@ class Server { logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`); } }); + this.server.keepAliveTimeout = 70 * 1000; + this.server.headersTimeout = 71 * 1000; if (this.serverUnixSocket) { this.serverUnixSocket.listen(config.MEMPOOL.UNIX_SOCKET_PATH, () => { @@ -239,6 +241,9 @@ class Server { logger.notice(`Mempool Server is listening on ${config.MEMPOOL.UNIX_SOCKET_PATH}`); } }); + + this.serverUnixSocket.keepAliveTimeout = 70 * 1000; + this.serverUnixSocket.headersTimeout = 71 * 1000; } void poolsUpdater.$startService(); From b79ed000eae0e8c72e8cd78f7e6b83a71be52757 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 7 Jul 2026 12:36:30 +0200 Subject: [PATCH 323/369] [echart] fix regression from v6 --- .../acceleration-fees-graph.component.ts | 1 + .../src/app/components/address-graph/address-graph.component.ts | 2 ++ .../block-fee-rates-graph/block-fee-rates-graph.component.ts | 1 + .../components/block-fees-graph/block-fees-graph.component.ts | 1 + .../block-fees-subsidy-graph.component.ts | 1 + .../block-rewards-graph/block-rewards-graph.component.ts | 1 + .../block-sizes-weights-graph.component.ts | 2 +- .../app/components/hashrate-chart/hashrate-chart.component.ts | 1 + .../hashrates-chart-pools/hashrate-chart-pools.component.ts | 1 + frontend/src/app/components/pool/pool.component.ts | 1 + .../treasuries/treasuries-graph/treasuries-graph.component.ts | 1 + .../statistics-chart/lightning-statistics-chart.component.ts | 2 +- 12 files changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts index d27d7db1b..915c79c93 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts @@ -220,6 +220,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest }, }, legend: { + top: 'top', data: [ { name: this.totalBidBoostLabel, diff --git a/frontend/src/app/components/address-graph/address-graph.component.ts b/frontend/src/app/components/address-graph/address-graph.component.ts index 2d4d77f13..542a185ef 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.ts +++ b/frontend/src/app/components/address-graph/address-graph.component.ts @@ -211,6 +211,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { }, graphic: graphicElements.length > 0 ? graphicElements : undefined, legend: (this.showLegend && !this.stateService.isAnyTestnet()) ? { + top: 'top', data: [ { name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`, @@ -451,6 +452,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { }, graphic: graphicElements.length > 0 ? graphicElements : undefined, legend: { + top: 'top', selected: this.selected, }, dataZoom: this.allowZoom ? [{ diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts index 86c7e4619..5293c7ba3 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -281,6 +281,7 @@ export class BlockFeeRatesGraphComponent implements OnInit { }, }, legend: (this.widget || data.series.length === 0) ? undefined : { + top: 'top', padding: [10, 75], data: data.legends, selected: JSON.parse(this.storageService.getValue('fee_rates_legend') || 'null') ?? { diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index 82caae836..4c3d7f3a0 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -187,6 +187,7 @@ export class BlockFeesGraphComponent implements OnInit { } }, legend: data.blockFees.length === 0 ? undefined : { + top: 'top', data: [ { name: feesBtcLabel, diff --git a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts index 9ac3fb01f..212f1c41d 100644 --- a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts +++ b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts @@ -240,6 +240,7 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { } ], legend: this.data.blockFees.length === 0 ? undefined : { + top: 'top', data: [ { name: this.subsidyLabel, diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index 137cabe74..8dabd236c 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -183,6 +183,7 @@ export class BlockRewardsGraphComponent implements OnInit { } }, legend: data.blockRewards.length === 0 ? undefined : { + top: 'top', data: [ { name: 'Rewards BTC', diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index 1223be9c8..f1ec24e9a 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -182,7 +182,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { } }, legend: data.sizes.length === 0 ? undefined : { - padding: 10, + top: 'top', data: [ { name: $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`, diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index 5c429fd6c..2ecba6e56 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -287,6 +287,7 @@ export class HashrateChartComponent implements OnInit { } }, legend: (this.widget || data.hashrates.length === 0) ? undefined : { + top: 'top', data: [ { name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`, diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index cc45cbc7f..a51bcf548 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -250,6 +250,7 @@ export class HashrateChartPoolsComponent implements OnInit { } }, legend: (this.isMobile() || data.series.length === 0) ? undefined : { + top: 'top', data: data.legends }, yAxis: data.series.length === 0 ? undefined : { diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 427035693..61821b540 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -240,6 +240,7 @@ export class PoolComponent implements OnInit, OnDestroy { } }, legend: { + top: 'top', data: [ { name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`, diff --git a/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts b/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts index d1c15ec2b..8991c251a 100644 --- a/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts +++ b/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts @@ -402,6 +402,7 @@ export class TreasuriesGraphComponent implements OnInit, OnChanges, OnDestroy { this.chartOptions = { legend: { + top: 'top', selected: this.selectedWallets, } }; diff --git a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts index d335e8b30..2eaf67183 100644 --- a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts +++ b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts @@ -200,7 +200,7 @@ export class LightningStatisticsChartComponent implements OnInit, OnChanges { } }, legend: this.widget || data.channel_count.length === 0 ? undefined : { - padding: 10, + top: 'top', data: [ { name: $localize`:@@807cf11e6ac1cde912496f764c176bdfdd6b7e19:Channels`, From a9c296c44ba3a226c00d520d2d8bd210abf2473a Mon Sep 17 00:00:00 2001 From: natsoni Date: Wed, 8 Jul 2026 11:18:17 +0200 Subject: [PATCH 324/369] Export isWitnessProgram function and add PSBT_GLOBAL --- frontend/src/app/shared/transaction.utils.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/shared/transaction.utils.ts b/frontend/src/app/shared/transaction.utils.ts index 8bb4fcbc4..1b27476ce 100644 --- a/frontend/src/app/shared/transaction.utils.ts +++ b/frontend/src/app/shared/transaction.utils.ts @@ -713,7 +713,7 @@ function isNonStandardLegacySigops(tx: Transaction, height?: number, network?: s // A witness program is any valid scriptpubkey that consists of a 1-byte push opcode // followed by a data push between 2 and 40 bytes. // https://github.com/bitcoin/bitcoin/blob/2c79abc7ad4850e9e3ba32a04c530155cda7f980/src/script/script.cpp#L224-L240 -function isWitnessProgram(scriptpubkey: string): false | { version: number, program: string } { +export function isWitnessProgram(scriptpubkey: string): false | { version: number, program: string } { if (scriptpubkey.length < 8 || scriptpubkey.length > 84) { return false; } @@ -1456,6 +1456,11 @@ function fromBuffer(buffer: Uint8Array, network: string, inputs?: PsbtKeyValueMa export type PsbtKeyValue = { keyData: Uint8Array; value: Uint8Array; }; export type PsbtKeyValueMap = Map; +export const PSBT_GLOBAL = { + UNSIGNED_TX: 0x00, + GENERIC_SIGNED_MESSAGE: 0x09, +}; + export const PSBT_IN = { NON_WITNESS_UTXO: 0x00, WITNESS_UTXO: 0x01, @@ -1601,9 +1606,10 @@ function decodePsbt(psbtBuffer: Uint8Array): { rawTx: Uint8Array; inputs: PsbtKe * @param rawTx - The unsigned transaction as Uint8Array * @param inputs - Array of input maps containing key-value pairs for each input * @param outputs - Array of output maps containing key-value pairs for each output + * @param globals - Global key-value pairs for the PSBT * @returns PSBT buffer as Uint8Array */ -export function encodePsbt(rawTx: Uint8Array, inputs: PsbtKeyValueMap[], outputs: PsbtKeyValueMap[]): Uint8Array { +export function encodePsbt(rawTx: Uint8Array, inputs: PsbtKeyValueMap[], outputs: PsbtKeyValueMap[], globals: PsbtKeyValueMap = new Map()): Uint8Array { const result: number[] = []; // Magic bytes: "psbt" in ASCII @@ -1633,7 +1639,12 @@ export function encodePsbt(rawTx: Uint8Array, inputs: PsbtKeyValueMap[], outputs // GLOBAL MAP // Add unsigned transaction (key type 0x00) - writeKeyValue(0x00, new Uint8Array(), rawTx); + writeKeyValue(PSBT_GLOBAL.UNSIGNED_TX, new Uint8Array(), rawTx); + for (const [keyType, items] of globals) { + for (const record of items) { + writeKeyValue(keyType, record.keyData, record.value); + } + } // End global map result.push(0x00); From 4019c0b55b92a0f63e285604cac3c18ea1a5ccd6 Mon Sep 17 00:00:00 2001 From: natsoni <46578910+natsoni@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:34:29 +0200 Subject: [PATCH 325/369] Add check to avoid duplicate PSBT_GLOBAL_UNSIGNED_TX Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- frontend/src/app/shared/transaction.utils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/app/shared/transaction.utils.ts b/frontend/src/app/shared/transaction.utils.ts index 1b27476ce..7598d3862 100644 --- a/frontend/src/app/shared/transaction.utils.ts +++ b/frontend/src/app/shared/transaction.utils.ts @@ -1641,6 +1641,9 @@ export function encodePsbt(rawTx: Uint8Array, inputs: PsbtKeyValueMap[], outputs // Add unsigned transaction (key type 0x00) writeKeyValue(PSBT_GLOBAL.UNSIGNED_TX, new Uint8Array(), rawTx); for (const [keyType, items] of globals) { + if (keyType === PSBT_GLOBAL.UNSIGNED_TX) { + throw new Error('PSBT global map must not include UNSIGNED_TX (0x00); provide it via rawTx'); + } for (const record of items) { writeKeyValue(keyType, record.keyData, record.value); } From f634b7ce297f17f20f1d2c2d0ad2968061e076a1 Mon Sep 17 00:00:00 2001 From: natsoni Date: Wed, 8 Jul 2026 14:38:35 +0200 Subject: [PATCH 326/369] Update spiral logo --- .../app/components/about/about.component.html | 286 +++++++++++++++++- 1 file changed, 280 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index c93e831c9..acc85d65b 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -45,15 +45,289 @@

Enterprise Sponsors 🚀

diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.scss b/frontend/src/app/shared/components/global-footer/global-footer.component.scss index 45a902d24..f750ceb02 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.scss +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.scss @@ -139,6 +139,21 @@ footer .row.version p a { display: inline-block; } +.footer-logo-wrapper { + display: block; + position: relative; + width: 100%; +} + +.footer-logo-reg { + color: var(--transparent-fg); + font-size: 15px; + line-height: 1.6; + position: absolute; + right: -13px; + top: 5px; +} + footer .sponsor { height: 31px; align-items: center; From b099929e38cab1f51337f3b2bd217c5e74ea51df Mon Sep 17 00:00:00 2001 From: wiz Date: Fri, 10 Jul 2026 19:24:24 +0900 Subject: [PATCH 328/369] ops: Don't restart electrs every hour wtf --- production/elements.crontab | 3 --- 1 file changed, 3 deletions(-) diff --git a/production/elements.crontab b/production/elements.crontab index c93a982d6..132e3dd8f 100644 --- a/production/elements.crontab +++ b/production/elements.crontab @@ -6,9 +6,6 @@ @reboot sleep 35 ; screen -dmS liquidv1 /elements/electrs/start liquid @reboot sleep 45 ; screen -dmS liquidtestnet /elements/electrs/start liquidtestnet -# hourly asset update and electrs restart -6 * * * * cd $HOME/asset_registry_db && git pull --quiet origin master && cd $HOME/asset_registry_testnet_db && git pull --quiet origin master && killall electrs - # daily update of popular-scripts 32 03 * * * $HOME/electrs/start liquid popular-scripts >/dev/null 2>&1 33 03 * * * $HOME/electrs/start liquidtestnet popular-scripts >/dev/null 2>&1 From 620d679c91389e3464151efe23b1ff0821d59430 Mon Sep 17 00:00:00 2001 From: mononaut Date: Tue, 14 Jul 2026 02:28:56 +0000 Subject: [PATCH 329/369] fix balance chart monotonic time hack --- .../address-graph/address-graph.component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/address-graph/address-graph.component.ts b/frontend/src/app/components/address-graph/address-graph.component.ts index 542a185ef..dd4022741 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.ts +++ b/frontend/src/app/components/address-graph/address-graph.component.ts @@ -492,15 +492,15 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { // Add a point at today's date to make the graph end at the current time extendedSummary.unshift({ time: Date.now() / 1000, value: 0 }); - let maxTime = Date.now() / 1000; - + const timeStep = 0.001; const oneHour = 60 * 60; // Fill gaps longer than interval for (let i = 0; i < extendedSummary.length - 1; i++) { - if (extendedSummary[i].time > maxTime) { - extendedSummary[i].time = maxTime - 30; + if (extendedSummary[i].height !== undefined && extendedSummary[i + 1].height === extendedSummary[i].height) { + extendedSummary[i + 1].time = extendedSummary[i].time; + } else if (extendedSummary[i + 1].time >= extendedSummary[i].time) { + extendedSummary[i + 1].time = extendedSummary[i].time - timeStep; } - maxTime = extendedSummary[i].time; const hours = Math.floor((extendedSummary[i].time - extendedSummary[i + 1].time) / oneHour); if (hours > 1) { for (let j = 1; j < hours; j++) { From ba5c2ff1ad930cfa66d8354d3c01a66aadad4de5 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 14 Jul 2026 13:01:51 +0900 Subject: [PATCH 330/369] ops: Add test networks to nginx warm cache --- production/nginx-cache-warmer | 3 +++ production/nginx/location-liquid-api.conf | 4 ++-- production/nginx/location-liquidtestnet-api.conf | 6 +++--- production/nginx/location-signet-api.conf | 4 ++-- production/nginx/location-testnet-api.conf | 8 +++++--- production/nginx/location-testnet4-api.conf | 6 +++--- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 171f95430..d688cc04d 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -175,6 +175,9 @@ do for url in $warmURLs do warm "https://${hostname}${url}" + warm "https://${hostname}/testnet${url}" + warm "https://${hostname}/testnet4${url}" + warm "https://${hostname}/signet${url}" sleep $delay # delay between queries to not DoS mariadb done diff --git a/production/nginx/location-liquid-api.conf b/production/nginx/location-liquid-api.conf index a6538c6b8..c20e9a87c 100644 --- a/production/nginx/location-liquid-api.conf +++ b/production/nginx/location-liquid-api.conf @@ -90,8 +90,8 @@ location @mempool-liquid-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; proxy_cache_valid 200 10s; proxy_redirect off; diff --git a/production/nginx/location-liquidtestnet-api.conf b/production/nginx/location-liquidtestnet-api.conf index c7a547e03..ede04b320 100644 --- a/production/nginx/location-liquidtestnet-api.conf +++ b/production/nginx/location-liquidtestnet-api.conf @@ -94,10 +94,10 @@ location @mempool-liquidtestnet-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; - proxy_cache_valid 200 10s; + proxy_cache_valid 200 30s; proxy_redirect off; } diff --git a/production/nginx/location-signet-api.conf b/production/nginx/location-signet-api.conf index c184c4272..5b01394ef 100644 --- a/production/nginx/location-signet-api.conf +++ b/production/nginx/location-signet-api.conf @@ -100,8 +100,8 @@ location @mempool-signet-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; proxy_cache_valid 200 10s; proxy_redirect off; diff --git a/production/nginx/location-testnet-api.conf b/production/nginx/location-testnet-api.conf index 4b9433bb8..80d61198c 100644 --- a/production/nginx/location-testnet-api.conf +++ b/production/nginx/location-testnet-api.conf @@ -100,11 +100,13 @@ location @mempool-testnet-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; - proxy_cache_valid 200 10s; + proxy_cache_valid 200 30s; proxy_redirect off; + + expires 120s; } location @mempool-testnet-api-v1-cache-normal { diff --git a/production/nginx/location-testnet4-api.conf b/production/nginx/location-testnet4-api.conf index 11600d9e2..a24126d70 100644 --- a/production/nginx/location-testnet4-api.conf +++ b/production/nginx/location-testnet4-api.conf @@ -100,10 +100,10 @@ location @mempool-testnet4-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; - proxy_cache_valid 200 10s; + proxy_cache_valid 200 30s; proxy_redirect off; } From a5dceacb42e7a01e94fecf5d2b6d83eeb0c23cdc Mon Sep 17 00:00:00 2001 From: mononaut Date: Tue, 14 Jul 2026 04:44:14 +0000 Subject: [PATCH 331/369] optimize pool stats queries --- backend/src/api/mining/mining.ts | 8 ++--- backend/src/mempool.interfaces.ts | 2 +- backend/src/repositories/BlocksRepository.ts | 33 -------------------- backend/src/repositories/PoolsRepository.ts | 3 +- 4 files changed, 6 insertions(+), 40 deletions(-) diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index d3fadbcce..404e1b7eb 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -114,31 +114,29 @@ class Mining { const poolsStatistics = {}; const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval); - const emptyBlocks: any[] = await BlocksRepository.$countEmptyBlocks(null, interval); const poolsStats: PoolStats[] = []; let rank = 1; + let blockCount = 0; poolsInfo.forEach((poolInfo: PoolInfo) => { - const emptyBlocksCount = emptyBlocks.filter((emptyCount) => emptyCount.poolId === poolInfo.poolId); const poolStat: PoolStats = { poolId: poolInfo.poolId, // mysql row id name: poolInfo.name, link: poolInfo.link, blockCount: poolInfo.blockCount, rank: rank++, - emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0, + emptyBlocks: poolInfo.emptyBlocks, slug: poolInfo.slug, avgMatchRate: poolInfo.avgMatchRate !== null ? Math.round(100 * poolInfo.avgMatchRate) / 100 : null, avgFeeDelta: poolInfo.avgFeeDelta, poolUniqueId: poolInfo.poolUniqueId }; poolsStats.push(poolStat); + blockCount += poolInfo.blockCount; }); poolsStatistics['pools'] = poolsStats; - - const blockCount: number = await BlocksRepository.$blockCount(null, interval); poolsStatistics['blockCount'] = blockCount; const totalBlock24h: number = await BlocksRepository.$blockCount(null, '24h'); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 24e01d4dd..3f1403518 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -17,6 +17,7 @@ export interface PoolInfo { name: string; link: string; blockCount: number; + emptyBlocks: number; slug: string; avgMatchRate: number | null; avgFeeDelta: number | null; @@ -25,7 +26,6 @@ export interface PoolInfo { export interface PoolStats extends PoolInfo { rank: number; - emptyBlocks: number; } export enum TemplateAlgorithm { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index faa8fce74..11d2d4e40 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -305,39 +305,6 @@ class BlocksRepository { } } - /** - * Get empty blocks for one or all pools - * @asyncSafe - */ - public async $countEmptyBlocks(poolId: number | null, interval: string | null = null): Promise { - interval = Common.getSqlInterval(interval); - - const params: any[] = []; - let query = `SELECT count(height) as count, pools.id as poolId - FROM blocks - JOIN pools on pools.id = blocks.pool_id - WHERE tx_count = 1 AND stale = 0`; - - if (poolId) { - query += ` AND pool_id = ?`; - params.push(poolId); - } - - if (interval) { - query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; - } - - query += ` GROUP by pools.id`; - - try { - const [rows] = await DB.query(query, params); - return rows; - } catch (e) { - logger.err('Cannot count empty blocks. Reason: ' + (e instanceof Error ? e.message : e)); - throw e; - } - } - /** * Return most recent block height * @asyncSafe diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index fcc218f7b..4fc3742fd 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -38,6 +38,7 @@ class PoolsRepository { let query = ` SELECT COUNT(blocks.height) As blockCount, + COUNT(CASE WHEN blocks.tx_count = 1 THEN 1 END) AS emptyBlocks, pool_id AS poolId, pools.name AS name, pools.link AS link, @@ -47,7 +48,7 @@ class PoolsRepository { unique_id as poolUniqueId FROM blocks JOIN pools on pools.id = pool_id - LEFT JOIN blocks_audits ON blocks_audits.height = blocks.height + LEFT JOIN blocks_audits ON blocks_audits.hash = blocks.hash WHERE blocks.stale = 0 `; From 94f04c0a2570e0006aad837450960b18ea78f999 Mon Sep 17 00:00:00 2001 From: mononaut Date: Tue, 14 Jul 2026 05:22:32 +0000 Subject: [PATCH 332/369] optimize block count queries --- backend/src/api/mining/mining.ts | 2 ++ backend/src/repositories/BlocksRepository.ts | 27 +++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index 404e1b7eb..ebf8fd40d 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -149,6 +149,8 @@ class Mining { poolsStatistics['lastEstimatedHashrate1w'] = await bitcoinClient.getNetworkHashPs(totalBlock1w); } catch (e) { poolsStatistics['lastEstimatedHashrate'] = 0; + poolsStatistics['lastEstimatedHashrate3d'] = 0; + poolsStatistics['lastEstimatedHashrate1w'] = 0; logger.debug('Bitcoin Core is not available, using zeroed value for current hashrate', logger.tags.mining); } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 11d2d4e40..20d0a6e46 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -327,17 +327,26 @@ class BlocksRepository { interval = Common.getSqlInterval(interval); const params: any[] = []; - let query = `SELECT count(height) as blockCount - FROM blocks - WHERE stale = 0`; - - if (poolId) { - query += ` AND pool_id = ?`; + let query; + if (!poolId && !interval) { + // optimized query to get full indexed block count + query = `SELECT CAST(COALESCE(MAX(height) - MIN(height) + 1, 0) AS SIGNED) as blockCount FROM blocks`; + } else if (!poolId) { + // optimized query for indexed count within an interval + query = `SELECT GREATEST(0, COALESCE( + CAST((SELECT height FROM blocks WHERE stale = 0 AND blockTimestamp <= NOW() ORDER BY blockTimestamp DESC LIMIT 1) AS SIGNED) - + CAST((SELECT height FROM blocks WHERE stale = 0 AND blockTimestamp >= DATE_SUB(NOW(), INTERVAL ${interval}) ORDER BY blockTimestamp ASC LIMIT 1) AS SIGNED) + 1 + , 0)) as blockCount`; + } else { + // for specific pools, we do still have to actually count the blocks + query = `SELECT count(*) as blockCount + FROM blocks + WHERE stale = 0 AND pool_id = ?`; params.push(poolId); - } - if (interval) { - query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + if (interval) { + query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } } try { From eb41d7d7a9dbbb498fc8b14f58508e5167033647 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 14 Jul 2026 16:47:14 +0900 Subject: [PATCH 333/369] ops: Slow down warm cache script --- production/nginx-cache-warmer | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index d688cc04d..a24898a30 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -1,5 +1,5 @@ #!/usr/bin/env zsh -delay=0.15 +delay=0.25 hostname=$(hostname) slugs=(`curl -sSL https://${hostname}/api/v1/mining/pools/3y|jq -r -S '(.pools[].slug)'`) @@ -137,7 +137,7 @@ warmURLs=( '/api/v1/lightning/nodes/isp/34197' `# SHRD SARL` '/api/v1/lightning/nodes/isp/42275' `# Three Fourteen SASU` '/api/v1/lightning/nodes/isp/16276' `# OVH SAS` - '/api/v1/lightning/nodes/isp/10796,11351,11426,11427,12271,20001,2$delay,33363' `# Spectrum` + '/api/v1/lightning/nodes/isp/10796,11351,11426,11427,12271,20001,20115,33363' `# Spectrum` \ '/api/v1/lightning/nodes/isp/701' `# Verizon` '/api/v1/lightning/nodes/isp/12876' `# Scaleway` '/api/v1/lightning/nodes/isp/33915' `# Ziggo` From 8c8f4149f860c1a023b580def2d6f1a1a020d976 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 14 Jul 2026 16:57:17 +0900 Subject: [PATCH 334/369] Tweak footer logo position --- .../components/global-footer/global-footer.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.scss b/frontend/src/app/shared/components/global-footer/global-footer.component.scss index f750ceb02..f83d936c1 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.scss +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.scss @@ -150,7 +150,7 @@ footer .row.version p a { font-size: 15px; line-height: 1.6; position: absolute; - right: -13px; + right: -20px; top: 5px; } From 6c1822c8fcc43cdcc3072cb30d4b7defa59c4d17 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 14 Jul 2026 17:15:56 +0900 Subject: [PATCH 335/369] ops: Add liquid to warm cache script, clean up SSR stuff --- production/nginx-cache-warmer | 93 +++++++++++++------------------ production/nginx-cache-warmer-ssr | 6 ++ 2 files changed, 45 insertions(+), 54 deletions(-) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index a24898a30..839356027 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -1,44 +1,17 @@ #!/usr/bin/env zsh -delay=0.25 -hostname=$(hostname) -slugs=(`curl -sSL https://${hostname}/api/v1/mining/pools/3y|jq -r -S '(.pools[].slug)'`) - -warmSlurp() -{ - echo "$1" - curl -o /dev/null -s -H 'User-Agent: Googlebot' "$1" -} - -warmUnfurl() -{ - echo "$1" - curl -o /dev/null -s -H 'User-Agent: Twitterbot' "$1" -} +delay=0.5 +hostnameBitcoin=127.0.0.1:82 +hostnameLiquid=127.0.0.1:83 warm() { echo "$1" - curl -o /dev/null -s "$1" + time curl -o /dev/null -s "$1" + sleep $delay # delay between queries to not DoS mariadb + echo } -warmSlurpURLs=( - '/' - '/mining' - '/lightning' - '/graphs' - '/docs' - '/docs/faq' - '/docs/api/rest' - '/about' -) - -warmUnfurlURLs=( - '/' - '/mining' - '/lightning' -) - -warmURLs=( +warmCommonURLs=( '/api/v1/blocks' '/api/v1/statistics/2h' '/api/v1/statistics/24h' @@ -51,6 +24,9 @@ warmURLs=( '/api/v1/statistics/3y' '/api/v1/statistics/4y' '/api/v1/statistics/all' +) + +warmMiningURLs=( '/api/v1/mining/pools/24h' '/api/v1/mining/pools/3d' '/api/v1/mining/pools/1w' @@ -114,6 +90,9 @@ warmURLs=( '/api/v1/mining/difficulty-adjustments/2y' '/api/v1/mining/difficulty-adjustments/3y' '/api/v1/mining/difficulty-adjustments/all' +) + +warmLightningURLs=( '/api/v1/lightning/channels-geo?style=widget' '/api/v1/lightning/channels-geo?style=graph' '/api/v1/lightning/statistics/latest' @@ -153,43 +132,49 @@ warmURLs=( '/api/v1/lightning/nodes/rankings/age' ) -echo "waiting for mempool backend to start..." +echo "[*] Wait 60s for mempool backend to start..." sleep 60 while true do - echo "starting warm cache cycle..." + echo "[*] Starting warm cache cycle..." + date - for url in $warmSlurpURLs + for url in $warmCommonURLs do - warmSlurp "https://${hostname}${url}" - sleep $delay # delay between queries to not DoS unfurler + warm "http://${hostnameBitcoin}${url}" + warm "http://${hostnameBitcoin}/testnet${url}" + warm "http://${hostnameBitcoin}/testnet4${url}" + warm "http://${hostnameBitcoin}/signet${url}" + warm "http://${hostnameLiquid}${url}" + warm "http://${hostnameLiquid}/testnet${url}" done - for url in $warmUnfurlURLs + for url in $warmMiningURLs do - warmUnfurl "https://${hostname}${url}" - sleep $delay # delay between queries to not DoS unfurler + warm "http://${hostnameBitcoin}${url}" + warm "http://${hostnameBitcoin}/testnet${url}" + warm "http://${hostnameBitcoin}/testnet4${url}" + warm "http://${hostnameBitcoin}/signet${url}" done - for url in $warmURLs + for url in $warmLightningURLs do - warm "https://${hostname}${url}" - warm "https://${hostname}/testnet${url}" - warm "https://${hostname}/testnet4${url}" - warm "https://${hostname}/signet${url}" - sleep $delay # delay between queries to not DoS mariadb + warm "http://${hostnameBitcoin}${url}" + warm "http://${hostnameBitcoin}/testnet${url}" + warm "http://${hostnameBitcoin}/signet${url}" done + slugs=(`curl -sSL http://${hostnameBitcoin}/api/v1/mining/pools/3y|jq -r -S '(.pools[].slug)'`) for slug in $slugs do - warm "https://${hostname}/api/v1/mining/pool/${slug}" - sleep $delay # delay between queries to not DoS mariadb - warm "https://${hostname}/api/v1/mining/pool/${slug}/hashrate" - sleep $delay # delay between queries to not DoS mariadb - warm "https://${hostname}/api/v1/mining/pool/${slug}/blocks" - sleep $delay # delay between queries to not DoS mariadb + warm "http://${hostnameBitcoin}/api/v1/mining/pool/${slug}" + warm "http://${hostnameBitcoin}/api/v1/mining/pool/${slug}/hashrate" + warm "http://${hostnameBitcoin}/api/v1/mining/pool/${slug}/blocks" done + date + echo "[*] Completed warm cache cycle" + sleep 1 done diff --git a/production/nginx-cache-warmer-ssr b/production/nginx-cache-warmer-ssr index 29ebc9883..e4d89afc2 100755 --- a/production/nginx-cache-warmer-ssr +++ b/production/nginx-cache-warmer-ssr @@ -58,6 +58,12 @@ warmUnfurl() sipURLs=( '/' '/mining' + '/lightning' + '/graphs' + '/docs' + '/docs/faq' + '/docs/api/rest' + '/about' ) unfurlURLs=( From 5b633d8f4d08c06a5845393725756ff747d51f07 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 14 Jul 2026 17:19:06 +0900 Subject: [PATCH 336/369] ops: Add more Liquid URLs to warm cache script --- production/nginx-cache-warmer | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 839356027..3cb803fbb 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -132,6 +132,25 @@ warmLightningURLs=( '/api/v1/lightning/nodes/rankings/age' ) +warmLiquidURLs=( + '/api/v1/assets/icons' + '/api/v1/assets/featured' + '/api/v1/liquid/pegs' + '/api/v1/liquid/pegs/month' + '/api/v1/liquid/pegs/volume' + '/api/v1/liquid/pegs/count' + '/api/v1/liquid/reserves' + '/api/v1/liquid/reserves/month' + '/api/v1/liquid/reserves/addresses' + '/api/v1/liquid/reserves/addresses/total' + '/api/v1/liquid/reserves/utxos' + '/api/v1/liquid/reserves/utxos/total' + '/api/v1/liquid/reserves/utxos/expired' + '/api/v1/liquid/reserves/utxos/emergency-spent' + '/api/v1/liquid/reserves/utxos/emergency-spent/stats' + '/api/v1/liquid/reserves/status' +) + echo "[*] Wait 60s for mempool backend to start..." sleep 60 @@ -165,6 +184,12 @@ do warm "http://${hostnameBitcoin}/signet${url}" done + for url in $warmLiquidURLs + do + warm "http://${hostnameLiquid}${url}" + warm "http://${hostnameLiquid}/testnet${url}" + done + slugs=(`curl -sSL http://${hostnameBitcoin}/api/v1/mining/pools/3y|jq -r -S '(.pools[].slug)'`) for slug in $slugs do From f72e6e9e5943ae4655c1ee17c4ab0950c12d52ec Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:33:41 +0900 Subject: [PATCH 337/369] Superscript bottom r symbol and make top one white --- .../components/global-footer/global-footer.component.html | 8 ++++---- .../components/global-footer/global-footer.component.scss | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index 9a04483e1..b3a78bf6d 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -18,10 +18,10 @@

@if (officialMempoolSpace) { Explore the full Bitcoin ecosystem - ® + ® } @else { Be your own explorer - + }

} @@ -62,10 +62,10 @@

@if (officialMempoolSpace) { Explore the full Bitcoin ecosystem - ® + ® } @else { Be your own explorer - + }

} diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.scss b/frontend/src/app/shared/components/global-footer/global-footer.component.scss index f83d936c1..5002d3725 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.scss +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.scss @@ -146,7 +146,7 @@ footer .row.version p a { } .footer-logo-reg { - color: var(--transparent-fg); + color: var(--fg); font-size: 15px; line-height: 1.6; position: absolute; @@ -386,4 +386,4 @@ footer .nowrap { flex-shrink: unset; width: unset; } -} \ No newline at end of file +} From c57d1cd959e9da0b1c891939ab6fb3fd2e7a93d4 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 14 Jul 2026 22:58:59 +0900 Subject: [PATCH 338/369] ops: Only warm cache testnet URLs 10% of the time --- production/nginx-cache-warmer | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 3cb803fbb..c919e38b9 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -172,16 +172,22 @@ do for url in $warmMiningURLs do warm "http://${hostnameBitcoin}${url}" - warm "http://${hostnameBitcoin}/testnet${url}" - warm "http://${hostnameBitcoin}/testnet4${url}" - warm "http://${hostnameBitcoin}/signet${url}" + # only warm cache for testnet URLs 10% of the time + if (( RANDOM % 100 < 10 )); then + warm "http://${hostnameBitcoin}/testnet${url}" + warm "http://${hostnameBitcoin}/testnet4${url}" + warm "http://${hostnameBitcoin}/signet${url}" + fi done for url in $warmLightningURLs do warm "http://${hostnameBitcoin}${url}" - warm "http://${hostnameBitcoin}/testnet${url}" - warm "http://${hostnameBitcoin}/signet${url}" + # only warm cache for testnet URLs 10% of the time + if (( RANDOM % 100 < 10 )); then + warm "http://${hostnameBitcoin}/testnet${url}" + warm "http://${hostnameBitcoin}/signet${url}" + fi done for url in $warmLiquidURLs From 7847278407f2c0ed8c3c3c1955dc43e9c1eacc77 Mon Sep 17 00:00:00 2001 From: wiz Date: Wed, 15 Jul 2026 02:47:26 +0900 Subject: [PATCH 339/369] ops: Update packages in prod install script --- production/install | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/production/install b/production/install index f2810c80c..e63f5e251 100755 --- a/production/install +++ b/production/install @@ -214,7 +214,6 @@ case $OS in NGINX_GROUP=www NGINX_ETC_FOLDER=/usr/local/etc/nginx NGINX_CONFIGURATION=/usr/local/etc/nginx/nginx.conf - CERTBOT_PKG=py39-certbot CLN_PKG=c-lightning ;; @@ -227,7 +226,6 @@ case $OS in TOR_PKG=tor TOR_USER=debian-tor TOR_GROUP=debian-tor - CERTBOT_PKG=python3-certbot-nginx NGINX_USER=www-data NGINX_GROUP=www-data NGINX_ETC_FOLDER=/etc/nginx @@ -401,9 +399,9 @@ DEBIAN_PKG+=(geoipupdate) # packages needed for mempool ecosystem FREEBSD_PKG=() FREEBSD_PKG+=(zsh sudo git screen curl wget calc neovim) -FREEBSD_PKG+=(openssh-portable py311-pip rust llvm17 jq base64 libzmq4 rocksdb) +FREEBSD_PKG+=(openssh-portable py312-pip rust llvm17 jq base64 libzmq4 rocksdb) FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc gcc13 libevent libtool pkgconf cmake) -FREEBSD_PKG+=(nginx rsync py311-certbot-nginx mariadb1011-server) +FREEBSD_PKG+=(nginx rsync mariadb1011-server) FREEBSD_PKG+=(redis sqlite3 pango cairo) FREEBSD_PKG+=(libepoll-shim) From d162637c678889ca5a41b1e70e9c8f4afc40a2dd Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 15 Jul 2026 05:52:07 +0000 Subject: [PATCH 340/369] footer logo reg -> tm --- .../components/global-footer/global-footer.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index b3a78bf6d..fc493ab43 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -9,7 +9,7 @@ } @else { - ® + } From 4b0bef35c9c68489d04978a693a858521fcfce7f Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 15 Jul 2026 06:19:11 +0000 Subject: [PATCH 341/369] change default preview img to 3-2 blocks --- .../previews/mempool-space-preview.jpg | Bin 34318 -> 70217 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/src/resources/previews/mempool-space-preview.jpg b/frontend/src/resources/previews/mempool-space-preview.jpg index ca76ba008a17d09f5253a700172a053bc4a4a6e3..d695b002cb4d93050751aa6687e653418072cfc6 100644 GIT binary patch literal 70217 zcmeFa1zc6z@;JWfl$KJul|~Sx8wm*k=?+N|1Oe%g7LXLA8$mjyJEa?yE&=J1`0s8uf(2i2h;VSQuyDu-2=Iug$f&3&$S5e6(6BHrp<$w-pkUx)U}EE3zI+)K9S9VTWY}z87%*uKsO- zzb){$1^%|c|0@!458Z;gfF!QKB zvTB3ck;PsyXB|@V>#)dS#I2&f`zC0wb^8oRxH{)NRv-E5txYT*%EkkIq9V_a@99g@ z90Jpq8V>!pR5|fiS6NS8?O4Xk`D$i5TZT(IEa@5Ddq`rr)ITB&XsDWWEmpPc^!R?p zUw64V^itL2F?Rd70 zL~gSOx5kEU$Cy|fYJ2v8uXM)fg_Cxt@8?&ZObHb%b9@Q2orj-$f)#fjo*+zh9*&K+ z9$gvfoJMnUQnF5PvNGJ-r{>q(G~6`iyR}|x{O%=z(_wpXX7fw!utS|cVZa zz7mF7L%Y!AEZ+2L>jOQB$wPMPJ6Tm$oioJd0;hqyTL&2lBySE_Pw(5Y9K)LWrl8@S?0YI_I6;7*?xg5Dd< zU<4~7=Eu*lt*72iIyfoyJkx2sDnL@iq4zcuVYNGYARVpm=$6i(VB!b4wVs2H_-+8I zV2C3LKjWuM#7}B(X&fxA$wsw0ymb4~#ZAUbeb2NBtiPoHAMpR6hMs75>XW-;59y`H zVY0gB>QjWinH3xTBwNN$0oz=4z;&)A>&Fh;jSj~ur!`!!&j4!vSFO>XwjXZh$Dxrl zU1yt|Test4euqY!fJUa;BOF!`uR_A@;{$ZJnVe=Ng*s zbvNK0_x`K-W0$;u4aawksPdJko-6jkjx!(mA5uKWA%LcPA8P8W)qS3RgZX4#>GKLU0zF%FfuB}~kdIm7_JwW#w zK63kw1po6#gg$kSgpW_+Fa?njkx>_AD<-BF@QFVOzv_JIHe#2DzIfz@uDiD9lovd_ ztJP!t_RxvQ67@0l$%@&Dep-HM{HME~M0e8;PioA|h)6d`r873Ti|aC{QuAbP@~U8mq-7bie2jQ-aV)>IZxOB zwSFcKw6y~k;{S{t*1weTe?^g1aq^wje<{NM;|&%snX-Zt97zA0X1K|RH!^dW5&3WQ z!HV?0;{rO&hVEwP1^}$Rpf5=L0f$1p!({~kT!E!3r*1#MfPUJto}^eVz-li}`jD*<5}m z2cR`QX5>9Ti2DYgWj>fc1Yjb${ocL(1_MiI{6OpJ^Jk;9bz-AB06<~1dDR|j*me&5 z@&<#_{gfms5P;XuZ4YZ@w7t*-&(GuZN#FMe1i?~v?A=?Jx*rfge}1V0Ao5)ZD|$kojT%M9#EkBp3)hjwR3m6OMT!x2rDLxEGErKJtXDdROJc(=0EY9s)snUrK{ zh=C$Nj|?T$2knl7!{ANs;XcD5WA^q5F)i5Z9{yyp*~_V~HU)UKMkd;iUM7B*2LKu$ zm6z-l^b$NPqoF2+cm1$xyoXkIp`G(#OrFg4cK{f$lK^k&tI4rSk~FYCkrYyS90n;` zTsU|kwWo?=0!RH;hjt;LO$dX=q`dx!SL|S(y6fiumHHn zE@+vbRbK=}p|7ZUtpl}w^K{|CyZP^;fv06C9Wz6fB=KLO16{s!k5}u%*aZXxysQ1S ztXp;8K!4@1{8%bN8Fug50O${D?TTDJk$#g6ESa_L5x?%cMFGj&O~CFIWB%-k!@3^` zP|Yp7Kqqwqy_D%^e`Ks9L?hK$jQWYu9TkwZfD-Z-TVKSy^y_i5U9A;$syB$}uf{&!& z1fi#Ib5OiU2})N8$-@X`AD$58+5E_@0HAYR!>gIR3t4!y`$8@6z13DK{gJvr76C|U zrzU;h_4_N;fe7){%`9qcjHNy-Qr>R={JtGi>9j^qV}1|?bHFogyS-F4&)s4@*j41mbvwLM+|JBkf!z;~}WB9NKmaHL@OVs1zuw_H6@!A+#Ow(pK6cGhq z(N}O_cEJ%-n|<7O{IoU4?TZBG6E-+29CpU+ZG9ICjDGaa#(PUA56|apz|lD`Y;f(x zR~iT!I3DSI7DL}D{>t%y;bpSpNtgS&E&_;0p7gM<&3zXQOw1%Np5^x)d~>eSoa#Gc z(HGh|^J9z`POQE`&etI57TNn8mOjg(a47P$Z8)MR3LS9nf+3Mf5tt#+>f2?byO4Dy zK+r=|QRZS&>=7&-wIgq(GDz z5%|##)$A#KJnQxy@E7{mg}pUoanB6w#SXBq^?nx(67>~1kK$RrI+;b<3dcW$3L{r~ zjZMpU{J(Izgdc4<@;y8QAanQEad~i;ZSA{gU_$WPrY`7z_%-g+#3*@G2U3BC~k4wd8`3pbmtclAd3tl$|#sJD;^k zFEr28XYkp6fBq}yM~Dz#hGFc}RfhyKjPFtbfG$sGPI2YeRf|-y?($yFg;p>;?l}Ee z7yOl>&Mn_oeFYTl%K~h5{~3L2k?#djnd2>n9ksqr@H=nBh+|D=FEk_IUgF;VG4=eF z!|_k6FXJ~G3%oBc2n%|aWzhiFw+zhT!Y^CAd7&L906y;U6YY1%Ul<=(5>P&FIt@U+ zuJUzc%3NynN9(@J1_1c7HyKRQw*tRxSo+P6wPNpoIa~RH1P%@F_WJ+L`DAqcoq12u zkE9AtC^WH=#RCp4Undj}7ZRD`@3MmmfnyVYxNq=X|G#iXL9;(0G6evGX8j>9VQq)p_e=H8w93|W*s1&P+B*Chu#ln{$y|i?vI-m zjsopT#vpu+f)uuF@j<}~Ou^NDfGu9u#`1W;1dcXx$XyCCTWAQEfjnIFnwOjkPTGLB zbl*mNf^Z}^D?tCIb}m5*#5(R12vDY_RU;1d|jXMXr7IPvS$FIxEMgu1i;0s;aqp8Wmz#P5*5aFQW-@x(Yb z|9b*>W4&#CTC4J1GH{A{z#pr=!sq*SY|z^KcVd}DE+BwoBcAM7_YHC$+P`qPcf*eI z*ADkq*Jgip&jOJFr*rq*iR@nQ0OGh$MiQk(6 z0F9n8rO(;z8wBdkWJ2aI?1<3@ttEbGM~r5_81iqdjiI`OI&F&p$F-!(FBN_`n)wc%4{GeR$=EcmT|$-o=qy2{{OSc-Kd|(1i;e zP6G_KFCaj*dJo3%mxDaMUJK8fJpcH#0;-)T>zBp`>WJG~T-K#lm+hgQA`2bQ2lt7dU`M}Wr%HtU=6OLk1$edaFrFJORU6eYgz_8kIKLPPn- zQx1T@W7GaCLj<5ntlj?AbFm*EoB%NUjOw6Q@fv-YHLTW({w^MP()SLnrE=*!X{1Q{@_Fjpc@b4pL_g=hspq$S`Ny7>6Cx> zU^2(7Y6>En9dpH<8kZu=P1c$Vhh1P^a|t%qG_C0ctfeA98ZVWiv-PfOT}*tOo0YM* zM=vO2Emk+g{p9!e$pCua^0CYN_kZw-g&T0U!uvfx<&pd!omfIt0G+9}F`;7INjmS< zkAp{Wo*_vZA5kkS2Vja?qYC$`K8O}Lke=gzd7EiukWDjp`vF17+W1U7pt9!SCy8VOs&oYJfj7G7fn-v>R?+G926%H*T5x?y=6dzMy0c*A5-{_7%SYz!Lv`hJNz_ zUp@e}T+JAqxO=>Dq4D8tWc4s_-37?MStahg5*=iAajW-lGWcV3_{D~!W9=J1K4|!3 zl7AoL!gA@~C;mkW__~(G6gkLeE`MJ?u>i4$*0BR>;!j%sG|+|9)jy5rZy8vgsbC}A1K7H;hvhxk!Vk0+(bc{-l+{u z3X);9Na&V)lSovERR0X_xn_>XsyVu)ZV-3k0acdbtF2;C8P9#i}HfG<{<)V`^k7UA2D4?o} zetK2W(2qbDi?A&_=5w~ zW5>|Fg5rQ^3Z;>T5TU-3lq3Z`byok3iM&FVF8x73{~=wXN2SVA_6@;3cd4h`ObXW& z@SB#MnRWTsl*8sLB)nb|me8eUphie8i?^tXU#k&RYol}; z2_a64kO9MOR%Sy_Q?AgddxnP3vBm^V!oyOcGUh70RL8}k^%?LV!VwX*b1Y?F^D=A@ zqYDg3iCV^_vsZ1AVq%N9ir5h(Oc-M@!5&B74sqaI zk<=?_9GPq>UaauE+kmUryl{KISF9wH;;qXgs%EbX2X(H6&<(c5ETV{z+>eV$5siF* znrBMFkDqlTJ)hjXTIouH?@opk=@}3>BI0TqgG-M?){4=)#*}-BDVw)03;R|Afs$ea zR9Z&&s|IN)8Po@8bX1m9%Z_0K)#3eaFOXSzwyMZiwz*D7NEutI8T%{s{iGFHveIRW z4zo1*EtrHA(o2FI^5ur-rJpb;aup6x&npo+Q-|}-in7>-M;gNxL!AM0V-B@n?<`^q zsc5Vv&o!J^yM&qQ&C->onXxP3jeDjPpOt9q4(5*Lr)J=wH10D^ZPYN6peOChe%%p3wE6VIj>V9U& zJjoL9l@v+mGMFoVnCd{;u-QDM)2rZ3NY)a5kBFH};cECThdp6IZssfEQgBF&kJPMJ zo5>l3Dwq^_!Z~SFi7;lCD;{ESIgq=md0QHM3&!QoCXR#=>^WxL9#6}z>5rKvbw|Fq zc`b8b`9{({)&eXo&#T+1Lom!D{^g>EgAGE`Y#jzwSp9V$AwK#OA+rrMJv3xUGh#@~Ijb;8=>HPL;Vl#;fZHD+Gg~KI$ffV>_ZW5fj zeMuzleS#PdPD8J)Y8KWYgA6U-7f9W<;-UE19M<&I63*iutvOVRRf^Ob9;(hz`vG~K z_jmMZEvXdc!bzVY_~7KLT+>Y%&Nn^jJg|6KUOXxU0Jsv7u=EcEDxdUy~41VHiW& z_r8J{@LF?h!KM${GRxpt%wGnsw&@tA1&0iinL8&eh#oK~N?*E#ay{}4FhmO35k*o& zn(l=5QlHGe?$2ysUKC{b(!?6u5LG6v_UH^q>3D-+p68LmXY}%Bm58F)9h|CBl+nxm z6a|AG8Pl9*HUuCzZ_brZl3OQ{-HvtR^!C@1 z^z~(PZKwQ{f9p}AsmNPOLK+NFc%r%a`&#lnj<-^+VAG|KSe|26;x)BfFQt!eVj|+7 z2Gf}n70%WJE>j%)Q2BjlKw{9%))ftiL*BdX4;`9u^;R4km>8iLLofGTdwwano2(vH zzDgqPT|Ax7SSEa-gfhV+U;6A?f<;!=x!YZYeVFgC3ZUOIumW6u(Ni?nq_i$`)pfy& zJu*ubHY7+HLHRKBeArt?ZySmN%`xdF%h=0u=4e*3g4&dJ$qjcrr^DVb|Cj8T1DX|& zjn&?{zNUWr)UVOi&FmOPdy4Q307eepoLBJ7jy3Z(E+hm!!tgcBKH|wO4{MaU~#l36W$T^rP=Z8+2wY-%9r4A z&h<`b5i(yDQV9Pzs-gZe0zhkti#TQf&}E5_ta-#8pI^NIzP`Dx72!4BoBm_6MO3Y* z4fMy#Sm+{}b7`LUpI%O9zdZVcvHQc$eR7cPEqLZ*UGbI2fI0fE&%WU1eQmJLY44eh zSh+!guwsX?tur$v;yHry4jitev0_X|?K<9fDs}v`#2CkxuHub&S(8w8yi6zgPHOno zOjDxheYn7i%@JF|&?GRu;{t4FBUay;LO_32D_oRGdt^oKHLdduuO1gTHoxiu5iP6H zV?+NW#K{zm5uTIRs``D@Fp-t$vMaD}YZc;1p2GujIr=t&7jIw=)xb-TU8z&#noMf8T>SY&{D>q!gZoqhendNi}3V| z4!OFEZk*+q!n=(VUq-ZWbpwGU?HE?Bk_HXC{#oL&Z6jXVy6ZhF__{NIwpNkkDk7yP z5AO%s5vLT*t)Ryp?EhTNecVy<;o2~iaz1j2n^F#RuFhpM;FSuTTHVip_1A`xl=CG^ zJe1qWx zyc$)L*kiEdGPl!g;ICo9Ry8mL_#Z|p}?%8xlfll8d2uyu_Xo0jO?KzU*Tc22ff zhcffKnEh72fa#p2rs>p`#NJ*YS#PHqj}t@#l$8#W1Vd9h0X0lR2(7TMUQLmx*vsF% z17~cbvt74(KoE6r{JXQJnsCBZmnI(DW3o-bZMtOgaZ+h&14oSwew9|&xnmLH5sd4) zDr<(mMTa-V?K+0F5`@`0zXX6OU{`Rn{@Tg`!Z0z8%z(S<_y45#4bcA=H%q%kbiD9J=pq#X`(VrpRyI04TzmaVD68Y^y{i6HW1{-7E7KnN4 z8>YsegIWB{-^9LuFh7Z@e$38Z;n7Rz;R!hx9>j_b=mSVNV!h6DbJm>}dB=G%gb ztwRPNjs_?xQv?Tq@plIxMSngl%7jbhd8lP)(U)FU{-~ET;CfL*MA{Ut9RyYE_XPSk zP(KUc;!heOzMx$P1^EZ=t-qjdE)!66t}e&#s{7sF(oF2(;wmBjw*71Vc3rBDer*W! zw_woUg8!wzdEg6>6(OD)Jqs z?3_?LUmd^x^D)=tlgJlGvUr(e9JD}VMu9`caRd&P1UOW`V$5nLpr8yAykej+`Tsb0 z)0WHOf@(lxVnKrU^j`&Uzgk7{UF0Gxk2&5bV{bN@I(my;(4e6S08Q|&z%eT%0<8Gc zzeMmj-j2(8%-43qx|CZ{o0P*j*CbMWM1u_k1JX6OXJus`%n-*Iq9f4fF<>i*`vt%2 zs*7(?-HY_8%X>#ju4x<1A8k#zz$iNdG?HzcUfo?#`%6Xu^J)eqA&j)f%UUoF= z!R<*{S@mq`0W0LsE&yyU4Idy&vgW~90?ES%iTU7;SyjMYlEaP%wrO*KU;ImpqUeT8-;^J{gPZd95biLER( zMeBKxqa^z>?MPyZmPe2yGW*iMs=iFovJP_SU|*yi`GqPizE@wDoanOu_GPm!r92kp zs%AB?+M$$3r(Bh$wiWTOt7OVm-fCNy&Dxamez$7qS)V|%n~~{;`9$%RTAJF>wc#w~ z63LQmrCfzv(+IQw3}|>6K%fhvUDCPr=kom`7Q0ZM=fTb^#`lIug)&Y#c>DfzpD!~k z^H+WG+LIZ{Xp~OVqOSLMj9|{dzhllcit1*RY@J{Ff$?qUV40}OZWpGELo$(#k^(sj)ARJY_of?0k`TyGjKr^2jqxnM5*IL_2g-;vRSeT zpQRyO#L80@*qcF~g2!;=A;z0HPnj+9#~;!%%m@^R&|oCP`6LA-fw~np-<7_cqcR|kU6NE6Boh+#@%X%_mnlmN93!Ny;tJ>@iNp_Vl!!oDd@S( zVk%y;Sg5##jUR^?`&Cm*6bKj$eJ-ih&RiP_d8H*pq7jj;M_Kbgfdc)cx z{7}wsYg8X!Z5KHLMG`Izy5DWKsW6EzRXpL;6I6l%92bbWQq7+BKrA+W21KXcKV4U+ zLMy>w9fCtMWze~QV{_FYi1xrYP&W{k_<{CJh=E}fwowil;-Cyv^T+51(89hq67(5X z^f37ZOIGkzvD&T}g@~9UK0R;}r_-M*M6r-qweVCXEha?2HyA|q9vo5rGj2)3U`5dSeratd_h0P>1$G1GHvD@@j=`}2RjxwZBKUiLlm_&_-gZG(=0ig&D(!;OGr@Qp}^> zm|JiSpN1LnTnh0}YE@CZKje6W&qC9WKayF!*$pYzJZjTmJTdq!f*>L6sjcsQ9%5}XCNF^7nw2c;W&<6!j=(Y70-k=XcxgylkTTo1I z@uhNuUIqnQsHwN$yrS;=2>Th9B0XvV40&B!J}X$Mc7L4DxCi&X?oBkF4fMK>W*16@ z`(tLt1-Qw&H!65$&}%xH`7>)icm|wr$ePE{7~3A&2?rEj)eM;5$i3B34+^H?F7P37FqC=dd z@RL%tZ<(N?4!eB&Kl_JP`do(l>2t~SP{sf3Ux)>@A1nt&c>VSO`Lj>gCm2~7cza~a z0?kmpR}sqf-wHWVVvIh!i_jzO&s~*gUfx!`&&&6oC5IKyig-GK3uz-nSdUAZgqM|e zeXF`~mV97x=d*pwMw@1Rx!GGGYeaK#I?u&)QKI+vN;M4Bp2R0S!2f`R68-qniRUHX zd1j}bwv;y=d!2y+vNi*2vzQG+3q*~sI*tF#v&kwX4{e&{r%f9nc~7VLf7;Zh)M%BE zuUl|69XyFMgLTw8=V}&(T2s z-TV{77SW7~<-JWGI{u&b*`c(D`V(W{BS5me{I6ObPBN|H|HOyKTtB6<3H0W9 zjP^4XX0JAri4!%}{8aJQhZ6DuR{K$-%s(0NNd??ZOX981@K-Q9bPkt8#UDqkMG@e} zFJ?@%@cIkAXJ|bAIn#nRwdQi)t&nh!pHT8Y6B=fn6mgJ{6#R)hkhGE+6IhZvrBadp z#K@uebM&)}{bT-HAyEWB^I^YMGJTM|DwV3`C){UH^k`m?=|`)>72wU2F|z)|Rk1V+ zG}=-5nM?h3LgdWNZyXwI{vp%?$nd&D8f~P^ zy&iXhV!$g)+>%zqV4%ef|1u%En+lYWs)aB};HIJ|z~w>@yPKJ-P2iR`=N6cot6#xw zX)gFu8E!5qurOCw$NgSeC_|->wTjf+Fv~0}8PvLDi|45+IyEA1&pxPyx{DXUD>~C7 zZ(lU1MSt-@dKWK@S9GpN-hpjU%X}9vmREG)tr!!BHH$-0Xh2_Jpq9BUUM!L5Qmecp z%b-@EEnW(d=nD8yFsPMdi^+)nHp^stQmgk6zj}I$nzt{72CQ76z#HTcW6CrS z)Rr5=uSqIoac(ql{Sf*q?=C`fAZF^!M4(mphfw>rNR5Y&QoUbH{#Rd~sGL5CgX+HN z!}Em6UF$;s&6hw@T!zny`3G=e%?~cDq2b_BVd0@+z@;^0Wer(a!(C>%A*bseih+rR zL(0r1ukf&7gH=GV9-d6dDya`!7?)g!A|(IIcLkuotgk=q`x>CShp@s6v=P9NeygUT z){{)=F&*4!AhGb5v1AEGTE-02XF^ly?CT_Ogw>}tobLEi2IitBMY0Bn(;MnalkJVB z4ijV5Inqrvu`Ely!wOSC5yS8hz+h0+8GDlI!P;XZI+xP1 z`*`IO`|ISVVl?QaB6*3i;O>N|R*fOtP01IdeT0w<=iK?Plu`6*5VzCt*TJF~dY zb!b6UIxpsE0V=m(sCaBxM?_D=cz4)yHFc-y5t2d1x~O!L$kOmC68V&nj*z^d1&R^# z2~a`6`5QIHXLAKJbR->qz7VDgdPK~kkTzkfpS`>c$QoENCczgjsj4-q2n&XS2LTqU z*G&YkspBJonz?+L0F|OvZ&^>6MoUL;%e{!_q+~Mc9xa9)c4-07rMMAGhKbswrURn< zGpvVRnz^?n-DD;QUn}0YoL`)?0pe;v>3RFhV)ZvC=DMdD_`=wh;Pqk)^zz(}q`x*A zqil}>zh)|?2o;)aAG|IFJ6RIIW3P0;%)_ymzN{}!bJ(AMqbhdZ| z!#$okzx_s6TLFT`5Q?LRVp;;7-Jvw~kzBA_V1rMgcdNg^cwKexIL+C?hL zVK+Hx(~FHPFb+npg@->WjZM>~?(nm`^meG7o;~~2zCrq9gnch-!^fDU2XY4{H-=r2 zBvdpn(J?CDr=de=crNIowIPKq5a>IPSoB)9&ON#-#vb+7<=0A)Qu0lLpJxRcGbaXz=6MWg zg|&O_aFNb!`mrfFs*IOiP%pi);IK;z%$gOr?wrk=rHA~U{*@D*_v$smfD`}TYI#r@ z<0T>+MTQk(N`v8eJWTs8ey95wcHF&GA{Dy&5)J#wPahRhv0S;^pc`5Z-^ePEir$ml zJF}TCc%%i>Yjel^5(6!3Qe<>c=A<4+%0u7rpq4A|uc?L4k&5q=1?#CbKcBuw4gapx zzf5SSuOo!oJ<-U++WSsg0FEn}R5`s|fp}m6GzAw-u3EDs3j_LH3RC8Dvu)|T4PZdW zg!5O<2AzmBDbz-AM!@qrE2@7XjE$IeRX-gJL1vnU{wh2@8c24$eJ58NFAO(B*-Qo694CA-6zr58RQ+sf-dDw)zrWH@_Ru z+jtjx!6uC00j!vA=wQL)?gwq2!ne#{v0{G?X)R#TyH60^#uB27zDs{8R=-bq+gJv* zh(|EjlmR73k1S$4L>0xCeA7_&Bf&=&X9GfQT1-9TGe95SD0KFVHemw?YS_x90-p6R zxUi9`UdSCan)Pucw+YXw;1g4KpZXoQk#88PX;ZeiwB>!g4jolk^6C=yA;yzD8jHL&$h z6D(T=?v#r;YEbYpV#Y@y{-!~&bR$HifW7de%6tD8gILA>0{!Jtuh}!fkKy-fB?W>% zgr_2&9DB!Ysr**{3&MrMoZ0^9aO01DHc+5aVz0VmBv;7n2cRgUx)NEcr}QAc)lO z=spz#vfq*GSOj6XJE2H0F-}@iD9v!!R0X9KJ)SY8hKu(t@cF-WbAr5oa*scq zZ)Yn?EkTkHzjCeopKijHa`9id#l~04|BRa@_qL;#G7a5wLfcQ-#em)XnYS9q?{{u) z!btY)8^aHVUg*QBRC!j6CuNCm(YFQ)5?rtPD{l^Ng0XPQ0#y5rG{%4|zq>}-TSs~r zVv?W!${T*cP$aOE2x=3p9_Zl3=W2EDA&>ePmPpkPEvObA3MDTJ8?7hsH_3Tds8Pt|h$GzU#N9 zB{lJ2U(xR;-rb1%olZ)O?A?j^Nf%<&9-;K#kBGAZP2`~z`J|Wb;EvmYkB-kR*b;Qr zn71eQY0Uey8Q7kCJY28~GoaybQ+<)`{ZR8%iS;Z{tcYjf38fNi)`{y0rx-JnUM8+=W!r}EN=A~^KkRJ#nL}=d zK-EH)>IkyJQhDvKSZ@IoZR@LOGZBXIJIF5BiF{?%l+=B2lN}w~|P$ z`BDi5ru`ulb41wxDG|x9^aPHQ$nZGaJiFxEa)l_-)@y%gZzj&Ampv`Lhb+kKJqY1qTA`|{QR7cC8OJ)=wWA`zR}eZ0W2F9x9bW3e1srN_tN zW;wpd)PC}d?)E<H<~N?T^p>x_0ar3~(1TfY%t~ z2_$@?KL26VRn2Bw{JTAv!cB8X)k1=1;a7r2kc4z4R*ng&Qu7Fs3U2=KsC;ot8&kL% z%P1F!vvlu{{hBM=IDV(;fzYJ=zWdMP7dRkjymx;PoAv>c*=$bizd-+C7qZ2pa z^#g&j?tXA6MUxkgR`(MXemApKRYdaQ8GP*p`_PkK4bAm6m_eC-H-joemM6S=h947G zV|y6}9u;HJJE8^u0$sQ}k*R0^N+BqtOn;wrwN)kG8!)w1{eEgwfVE9msgI;bYp;fW zmoIgjyxG0)7cAIS;6bl87eI_){@&eklq%IQkfiOF%OCc~#PPz>2PCl=rAiOgM@#ss z7P>N3PUhe$i3Hr|pL>T3*<5MrFR}L<&LcmZ}Eq@YV`d)LC^pbk#8(0wh zSjUb10pltLeD3@^4-nx^z5roalpTa_z5e%6zwpvd=mj}2^(;Z?7Iz?ww~rv~4o|*z z4tL%;^m*q{rtcvHCFcZc=L8H8g29`#x6|UNBqa5|ba9a!|+;h>*AtAwdLC zNRZYJ5d|V7$nqah-Ou$oM}<8{je($ILr|g5QDq=pksw^Zpi)D)UPQeEK{bJJmEne< zW?+4B-l**0p6(A!90sr=?pXUV)IpoReBWNH3j}7DGrmAf)i; zq~j3M{~2l!gexY5tH-%OVi2x~5UzrR5Uvyu0btJs_!ZQ;H3OMY`NSgEm-_b=)08E} z*oLlZiaDkPWcDaNv?W-1#+bq@-#loX%~=_^D~=~0pEqeQQGKiQR67j55tV3O|}u@B)M{HG*6 z;7OeV_{Ti3P_U@Th%m5-@G##_>M(`mLNTzgNy%7P*&bTE7r>D-3(7xwo>bpQaYH~s z*UF|4N6&*&_-=!Uj%RX8(e%b;cKwj>{JQ?luYc$R&ky-SpZ#8K%t+Q2l}-YEdc(ki zuhnbvE%mpX(pdPPmfyZ1T$x$h^iIi)jC^FiAanBxR0!*0YWZEIg8DN+N8+US02UiV`Z*F^b`F?d1$p*OZ1%U4T4R6UN*%@ zn7tjwN%s9Z(N7H@RN2T0jScAGN%|z)5lkQ0x8R|D5-J-juW!uV=cnBm}CG{n#$^FD`MKsV~y}(=4K(9;!C$JGHYJv!H^?^pF1;fUb?nW&=8aR z4E?BeYm6tE{&oHPjo^L>i{_+j>JG?!I_Y;G1={jAwC8AiFj*0swrvVZD(|om%U9GNpX*xnv`My1yj+sABkC`)Eg{6w#u&uDrMy@f zYOV9Ula@|-rs9;EflS4YX?reJV@Ayy@nn8lqJmGvX|3wz{euukG|FlUV{L&a*{{|m z1k1f%_bInWMK?sVX-F-%JQaP_+lR5dU+C2qEk2MMF6jDH`jzUmt%Dj==}iuew}2Kc5mM6j!E7ON{)J#j~SMN7A&PD z)Gke$BxOK=8K-^1G_TuGBHe;QnD`yr z&iDq54GfG>S+6IAnvI?L2P(}11}8fGv=-9w?iCJ=q#$t_uI&()!PhVJ*);8>KIRD9ikx&JudKWmJOey>jWrkHlN?N*ng_mrBlft|j_e!|98eC^QspG23(N0JUZeaF_Vkf~3puXu<~ zG7kMQ)pAdgDLf{5KBbwdzoasET;;<(18&YE8}1EvX0=2__#_iWq6aJ~%x$AFA6?3b z<-!R=w^ed(M{`zGowi^AkGQ6~ZO52i7hD>`-lvDSIl{CSotiDYH;gG6R@vvW04h5| zUtiM^&(IcyTE7iU%-8J{CUshy+ZcBPxRg^$*)aMz`rnwDuGext@zPbVtbfy@eZsCn zO-5L{AH_~6!gEEn^e$EDHYN*VS5(RaMKA7zn)`kkk3yb!>r7PUeYGZ<(SCC@Xw-pU{^G1LMNV}ke6TV7a7>0H5^v1U!HSA4Ih zvTo<>cyOwCz2h2JRz-Qt$%-kt9~ADVoKBiFkc5P0P3D?y1d!M?i- zA=ypsX-;c;^*8nTubEQEB~F@9Z#^O-v$~4qq@-hg{S3(cjWu@G@8lLO-)=*Ub7)0L zl8kH*ljR(HM5O>-Sy_gjCeH9&h+I?1c`%1?QY|viLD|*V&o(%HKJ!L35!Q+$?#i?F ztPGs7ozywp*CMxO9C6;zSUXrqt!E3|yBE2PSCwTxUi>*XBk4M9hK9ONe@ghdd(l}0{tN`Y%~v;v=YHKYG=0;cIRO#j=DsCH2v$Ej!UM_RuhFKfV*VDzd=GT-iW z(ApYe2&=eN8hA1!fxNOc$>@DeJF>Swi6&wnb^IDZu_D|3Ort)z?&PFcQDF}MrxusL zgK2zYo(h=~&}EXO$bd~AYgP}XCWbSV!SC1^-*a6>a zV-?V69h!X< z;`+8X9goB_Lr)=pVMg%JE!2$%0*AUN%PLsZTH3qXS-*%du?Po2Hx}Dy|_ilmzlHa9Srl5!$RoBd5 zYk??~eLoaQWdo@W@~dK3!3CN4EABD$q=jLx+3UKfdF~e(p3$MEe}Pz z++!ZmheS{gd_vS7B*#7!VLeRa?xw|4N)vmjAs_3QyiHJ+&BOw!;8FKaUmI6d)c&|l z4h~ss2X#jdS9WZ_MyBBBY-FJhQJQG%WyJu+#wFs2v=j;Q+mg<(NK881Cbwz2@Mu^t z5uM+3Vdv^N@V|$@!;wm|04q~${bbP>`-x&=847867Dm4-YCt*HBYJrI%l$~Sw=<8h zaLTMW7jFu9p9rFLHzlTb%}e?#X1Y%lo66P%3W?k(A*pjeB~##Wn!kaEA8B!&P%!fD z9b8s~Cib#VsX7hLunfsmhOaj)xC%7+S;=bez3Y&$M>RzK|JwTsuqw7L&_j2Mgmei? zD*{R*rHF*6NVk9-T0pu%DM1>QP{INv4&5Lrk`jkjy1Nf=4l4I}MXz!H@4fGPcV;up zjum^Ywbx!VXJ&T7P2G79mfOR~mID_>Prmkhd>L8E)0~;KO+j|mI}(C4&0)rE;`4!R zKuE0PN%*^m)qqB64OgIi7t5b6;}T(Vn1OcBvJC~&^;)!M57^=5F80jR$4mR5th|M} zCtXi84mjaFLNhYVdBIARiK{KUwfVfDu?tSQur5o&G5U*Luq2NF4TwuSMPtkg7icxI zNUT2OQt6O+;WHJtQ%6y!o-z+xC}eW12TMLZx3%<8rT8hp0K@H4^Inno8kVO^e$NYf zX`YYjcWS(Jd=U|H)%x{R`ZeLJ4Ykh;R9COFeN~ld&w9DdCwn@)Anc)31EeALM(mxs zbBiS2+0|~_R$w)-`3oxlO9|;s`5pPM+6z&u<)b# zhaTk@n&g~O3mt+c@I_Aq6!7~9Nv2%97gWds-jaFa5uO#WD_f+*C?v+@@(lM4xx9-U?Z9dC3@91PXOY80@k$8o``m_*Eq_z%3 zC9C0A7(1*j>9Cwx3{zHI7e{i%ar$$w|7g#A27awbVCvAy4(=D1G$}iYRiZ#^wyT0u z#NQ)74P`w#kwDY2n4}6V^z?6cRXO7LSfpnwjm*vhpbt@GYZViI_CfM>-fc9Zl%2HW zt3r3qR6f`P`Q@7RL3p7yp$`7HA1X;|UTQ?G~Y4KW-*vbv7J(mJgq!ZGHcqBPbZ{!wxMXCG$cj6ZeH?vU=eWP_3C-mYD7oNGXHWW~r8GLGb% zOGx4!j6fYo4LP3Xz)ReB5!@7{tg{C)I;Ns;A}(NbA=JRN0`iGZ`$PFW7*$+9F*$sj$2BI>sb6aV0~h37%E^RpMCE>;FUaBRa27i z*i`b)gw_Xi^R?`YHhtz0(QX^{eo|2XAqt{3`;pk{dr!3usD#z^8ggHEu)h&?v=u$Z z#p{RC+5=s^-WOWYJKd)R$j1h*$A3{i8lUC!;#>L5#;L`Lgg%U10Cgr0s%y@j4ia%Q0*%gn&@$0DDZ%ZC}(w9Zf( zlYJNQ9sYxfXALh0Ck{Rui&&2u|9vzH#T`s2PUyX8Z{ed0P+rbAqwCV3r{l?F88#Gel z_;5IfnTsZ8_#*KPO`mZ$#Pgu1@t!~*3BL$YDNcSi08Rk(v^?xE)_|ra43Xpd+7lu8 zgaYlLM{qd|^gfE>`>cuc^|}ZH7h!m?u|~duGd*9*N7Wuq<>$HC=I{Wo!RnSQEYvccGALm>5nv~&!1he1&f7f;x{ zLGc^8lCWh8&NaEFf4%7bKKfj;=sNo>Eq&(R0Dy$joxmtlfby)=*eT64fI3iJZwExL zoE`g3-rWaxHW(|2A(s6F*bkd@!k&C0u2^rE7&d#Jxz|@N7x#y39N^=gPK)f6c~=K; z4nWX{%}N4n1K@;TqXO6m$_l<8hY$%AU6pG&UGnCCnn=AluTn)biHO@@Ra?QGw+j4UCZD5SaS1Smbkg|^ zr)xotx75w8xrp$~YGB8wjBXU&V1gpXo?nHt40?(qEh+K^#~4mWDvQ``V5*$w+rmqx zc+}yaN4r~el>qYsKXHH!wTphQBbT%z3s?tan{?Ob1=+YDrc*w;^QN(=(}G|?ID#6c`qXI0;$%G$xL<-tI=?a zJ5PUn0}1)Q-Iio3#?rL5RpAHSG~9>x$(@nQpw8s1n92iGA+}d^%gB*&0&;I2VQks< zM^aQ#Hu@@e@DQJCGVUoIiQq_HQJ&VQuK2-+^ zk`#e4=qwTM&@AI9gX;!4Z5xNUkrkomHqyy!z@0=%p0fMJ>AQ@NKO~t-hIrSz8RpJX zy_ri{%G+4Rro?%C+~Y+T_Gpn@ndz&Bpm?WO&!X?>p}g3k2<7QICfbG*cn=qna{n4y ziWYvL$1C?bPXU;nd=7XYp-m ztXF}TM8uDXs^FKGWj=Wj^>W1SEz58oyB99EhRsKJbuVsq&TA0P`Yd|X0z)Y44x!l4 zxv`x0VLpvKMYcr(_6!eX1>KHUt~&|yR^er!kE(j9dMWW?OjYZJY7Jz*SFn_(dyzU9 z$ZsaI|592PpjX_97tmvcZ3<{1N_n!zSlf4v~}aHVGJ zIS*`bsIJ(n!wKt@=KD~>L>V15^C|&eO5YIu(DMRkwnwgZTHi-KcS^P(wyG57RFG~j z)n~y~FMX#8r=Qu%?H;eOsxK)9v0J>2WbA4`;Te4NIrhJq&&XJ;pF`NhaXT1~m1f10 z><=<%N6W4H2ipnz2wk3GwJ?`U2Gj}5WIkZ-=S8myzqnqA`n)fGK|+wyIg0w^jTv{v zyF?VZd6U_Ehm$K`^LTO7xVg-U zC8VRHqhc=_J!iwxIpvf=M{CA&NFLRIh)xJLT*y0ybF%e9ahz&WPfu3bm1=cm>h%Pv zH>FTh;ss0+Rh=8TQMWlKBun1tu`8w|In{kE4@NFeyIO7ZoUrG^v7_r0&pYiLdc}&n z20TEwYsPoXjQ2n`TCpW(98Fg`CttK*Psxz~l+_cBtllzpbc=8_J?f4xigaaG`EGoe zRAPqPQqhaDQo`nR3-63h%%0uMAmzl3?D%1>d=aVS1Z39D^ZH(%!>==RtCY+o`yyU@W(mz+?8Q+p5TSjc!Id)5(fR zwU7$gh#bAtse^hip`5mRQ##9uMqHAec(BnxqoiA@ND|ZfluYwO!k5gMbF4b6xc6ks zk_-Z*-$iUVGmzRF;4jwFNpuA`Qm|DNzDZ$gbnAANHMupy!Nt9(D?~Cj#MOPr10xz` zSG-qT(|wMYEpl@c^V-?K5&^-@`z5)AY(^8*6*@NRw5%0^8=z9$QcqIgY1THfnPXjy z1zHUyq4sw@iI9+o?cAa%8=QuC19q$!G6OtA-i(z$^^}$$Sqad5?JI*UaL?{57-q#)VX)87kLOQsb2a- zX40zcfgDCrM0ND*672QxZ+$E{(aJe0{p6N&Yts#~+QtV#mAsXTB>EKE{Kbj$n{*ye zffwwZYVm8HfvDXH^0@QK?6PgeM#k$&3K=Rmk+#p^t(~P;c5*^Ka|HM{iFLNi;(5^q z*9oK}t+Y(<&Df(;C!WL#;kwGMH&jZ{i~j$rwU>`rLEfDjFmSH_xxP6k2h=Cffma-lcc3R;g#gdH7MZck3Pc0I{} zXQ_egHpmGx80u7Lr5S7xZeqa*d=5de+LB5)&kmEwr_^P`ZM#{aLKJm}{4q)(-^?&F zVV-;$!5&CZ+&D8qXXehVXa{y-9DN4&9%yXIm9ip#g@`JhwdTaEUl>|du={ymwvSPl zg1z}M#NF!n7Y%kaj$0JlQooi`V<3NBYK~WJHo>f&uqMXr6_S7Hk;8=GCCx&6Y=go% zO;l7S>j5l-4F3r_Wc(JT-qE`G{$;20r*OE599SBXs1q#mXAAQsWr-Zb-Qq<&>-BGP z^(_SGP(H%Y+XGpCm?LD@(1N6jTO7NN7u4N1Azb`{J??B-mpG2bCdqk}%;4iAZ!;QW z(W(LpK_E{m5<4HsO#e!pN>Ek84Dem68HecrzFJOWGJ+ON%QvG-a!)L~B`kdxCWJS% zsp4D}ZE5G4YF?+}Bl87*LNk3NIr0hp!&S!nfNfjU9PbKak zhG|P3yY8ot%Ory5Cf>6LGU>C5BBakh?_f(a$(5Xk>ndhTx>Mm&z+-z3rin2U#Xz2i zt58#IA=tG-%yU-jo`S{c{5&jVRKjX98n4%GX^Zwmg)GhB^k)oVCm6z589YCylzYZ0 zu_m9>J>Ue$8p8|knH-NbIUaUdoHfs~W~Gm=pn&+<@i3F)odD|{2+v`9APULN8V4pC+kyMA`H@l?U z+ate!k#EP!E4BpAB%#i`PK?ai&39DuYUi({jv-<&fi+sb>?0JT`oWWhFP{HOO1nH{ zRThvR4Dr@cl$K_6vxHjK62;I3;s*JmGpP0AoYD^-zH|?>{FRHwnd02$ugoa>U#J`LOh$IKjdFw@Z@IGzK&z=)3opR=88Prdx; z(0``Jd014mJ%`qPE~H%4g50HtDYfSIRmlc4kDC%p-`7W!54RLI^v%`3D7S4 zdD7(Z0dO!zIGCHcm;X<43j@aTHwy=Y+;O}a6=Z& z6R-YxL*4_4zi$Pgw0{+9-~Pz}`=|Oz`xn5^*YUH%Y``!m6SD#=$t__5Fc)gFcj7fa z$u8ViX76H9{$elp!CWxJrSfHmMfx#_8h_3;@Q9KTH?2@zhJ%&%W03tEn0l6H-A#kR zr>vmm<8JoXiJMobTmcM20O6xQhXL-BN-$_c0i_V+iPtRsv`=~`Ui%U7Nhp9$oPSa$ zxTQ(x__dGQch7^c;<}697QKMmVqt(~Xr)UbK35c(6*U;MJRvYp8a50W4rV+hPEJ9I ziz}I%Luyb}07Fq4?bg)r-HrT6i-gHEz-M4U6Gk?z?1|pD1Y%>>Ra?wanv;*5 zW+L(k%`C5$Qx`Y7WYu>fRGj>1#tSM zvMFhdMMVipqJVh?)#W7ORFX@OE5McfL4*Q%?O+zc#cC$)%|n>7Fr7Q|4<-^C5j{#Y zheVbmeOw^)vT=tG_sxAR0ET?V9aminGah?|jK0auH9wgBYDZonPGtC++*U?kW)v%? z9kU~LeS_kiO0;QtSC?C?cJjvrEwT?Bv|+RBOGIc+^@!*%oK!zp5ce6zr8lw%wCK-% zy&@x9Y9GTm{t+^@gXv84u_}TET$oM%GW)9MX=>6TS$g>dG4&!{w8$u0fARkIk5p-r z7Ak>Bk8`hb_rdq3AOQURnc5xY&M%fkG5ge9#`!M`lqEd@ToEpOo@KbENdJN2<96)| zUDx{W&Uxo-C-y)u63n1#o)()EoI(zZBeh=Wxa8bA6<(z5s;~8=>d9z;(hs#hCAH|! zkcTw-WKe-zyomGW@(1alW4tC;I?Pm7Y1ulQGnAG}WExgz1(PDMNmvyVB|MF(kz6M; zX*Ax)ZmZpzHOa_LDFsfoibfshBc@BQr*CRK7Hew0U#IUPrbVv5+SJM$ zbsUVCcKv)x8!_Rkvcu!ZSKnCk}^qfJc0B zftUJ`LGZ_g2_aDMf9XF7{3n6`B=DaE{=bueJN%6iBK2>l0GEG36g~TB2VQ-H>ejV? zVbXzfnCm_P>DmLWW`k!28`sv=)1el}I~!je(w}d8_zxv^0E;bANl+CrF^_2uSyf%b z1n}$y7NdsTe*pZZw}-UPh4)`VD*&w4`ik-{HeDA7{u zxop-sW{{3XyId!Zgeqg5-UrwI1-WYi$a9z{ARVt7T77vRaP)T0tTx~+G^&CBO|dKI zu}SAhUCO(4JzIVj`&i}Ue^u-$?O?uM@K_s|?}dC0(4AiSe-P=<$gKyCBfe=C0Tl>=HwZdupM7fbH*`WL;niyNGrV3O?^(K2%PAnlt6H`rq69 zHo05?`31f=>fqe7lXlZ+3e(oMi~kyONEd*dNjnA%X+43~OM}!lo8>P2+pK;AXbvc? zyWSQucupkhNhgt^KYLK*AL;R{j$`pXFgbBcxwqkVrJ-Y-RkCec!M`W=Jh<4I!YBcX zCTss5&!0uqX8=bo3hz$(zsK`u_Ia~WEKK=7NnyRAsYsP z#+^W5{^A!Ld2Vswzfiy+z+zBw<=f50wq^8TPQ5;~@)1n$M=;(L67 zDNDHnYQHn+G!7{kFVlJ;jv*Ll4iZK6!eTmdA(9?9><9pA4#4KQ?SX)F*ggU%Vf?U) zJ<#p}(Oz})`db7urQqXxplJ(ZcbqHWvNadpva&09k?v7$GwI+`XcE*o4AQ2ZVXXkX zw)|N%fJSy_5yXT8d{p2OLf#PI?^7}X7csvS&DzX~1E5QiKpgUrJy6d|8*KLypBl2} z9w@5-vTYs!PPz!a;962I3i|?RzXw8;T;41#V)iSw%tU6w8pql!V30L`P%a-7c%8Ui zZhMux#~8~V)-VR0E#!dQWdk#BtuyU`w&09K08|F|KrQh5QD3$kx7{zN@c(uPLW+cD zsa0)r#a$@dg7(rOcR($6Mn~FP$eteq9T2{s1kkAxb63Z5tsA`O*V00|vv_^V%y~ z*z9MvKQaI1_&Zuy1uy}Fq4^6AAe)opwtAR>zcYQLq0&tqW=(KNxdFvKk00kz*z0({&8N?1U^2n>a<2=M)Z z65zdudnCaFF@PcO0VOOWkHq&S1C*8=)clSbH~?rs4ZR2hIvh{~LN@H$0kPj73t!;_)G&-FH1#i?`yDkr z2cQ8pd}TP4fELOT3=8iI0jSwO#z@JB$4Hj|r-y(M2`st?az`jQdZ2};Ur)~v-@gS( z9ft%-aQ>2kk47Cx8Vb&axa}Zh5vJ$%uY)A$t*?V5`EP?Hvo!!y*B)pIK@E`rTT=-6 zP(Y5K>6!m6QU!k~C=@WZ;`P z74DC~PY|bO=GmNJgptPd3W1mzVwei)1(tV13{#<|--M~Xxp3(AfzJ|v6U&73o7fgwu>%6;?~@`Lt2 zdoub5uwQulF0K8D=)1N52>uTZe(65NFLyIj{g*-hXFdIYiW9SI|DcyY;&$KF{1Jv9 zX<^^b{HVd7bglQ*{|fV;aP=STe-ijl0{=4vrf-?>msd@Zh2v1n=KF|2dHJmFwPX2Lzt$K{h{;~5#7)4+wu+<0aL z@Zk&i)$`kPtCt_&Ude-&%}_$8q0e11fO-LrzOvnUaQLif=W6S4X$7ZRA!|=KLm}4W1!rzPyDO(FQRT zO--o4i~G>Bi*`0~0-)KlA}Vot)8Ez6zQu4Pha>h#sh7pkQc-CIF1ZzwrIj-A%XffE zn-;a?BWy-K!xPH4EiB}=wxnvu+$?yk&c^pQ!RGWn*obDw?DjRIj9Jq1xI{G#m+Ea> z#8fyp&wJe|zQ|`Rq=Cu3fLw=yuO|)eJu9zz4X38`rHCi|{M$-^b0B=#VHn>j#4no%J+JL;R9@my1-_vs;VlJzr!vp$bo~?pn5|UXcYV|VEOpa$RSje ziZyqwA*Rp1=^#L)V%|a_2Ot38jwlcoWgH&;f?rjQl$4ZcP85gWa-&4$QY7g$(g>d? z*&{nS!WBV>F#Kd=31YI|cWBmDw#9V~O;lBHs6S|55}^V5L`n+p&VGMrJ}hLe z%;d0hozOMZHBnU)6ya(--$&JUNMHCv*o>lj4F)6Lx*uE3Wy*+?Q2v0 zBq@ukU$)9I`HCIvL#RkE8k%+fOEg9k=kN}rAydE+t44ANk)Yo(%F4<^%th&WLr=@hbSuUhC1&q^I?7XWnQy$9MT6R;n~SG&&r0j%*?Djmeh~V zLV&y9AL*Z5y>RWY?qrX#s+MwPLuFfLj-9i1q+fo{A-xC+>q(tBtP@8Tf2SJ(hxGym zS4G%i-4Lbz<$k0?sa`z};wXZ)&!YK6B&}REydn+h9zwm7p z5P;nKWz(DwnK_IMW#0x6MJ>Y)^U%_Z>i=kCU0<76zhLt2p_7xF(+7@gk3!Zr4rQWZ zg62!CGxk2@u(> zjWPcA^KW3GhiHivHvYypmd55#`N=F;WE{kB6U&DMKDmF7dzRHki?!Ph71F?lqx$5d zkkvy)NLx`R^nZ1aB{6^?|=5MbVOlRTiskSQ*+O%skl;M)?wK z^~W6AvD07a%hhl>R{z#6!yzv4%9yk8g^F`#d!UR%#}te}?7-2x#T*jWW#}SCbxu$h zj72xuLxyg+o6YdF@sH@-+Vb31C|em4*#ot@_ziAZ#=A{cx&TiH?U$DBf!aN{_du6% z=640S%2u?&+qVTQplPLzYg3lmc~`u53m>w~b_|P}1MS16ENPiLhD79^l{-gWao==n z_ub@7#aq(6jPtSZAz3{D2{ajG?ienWdsYQB`BIJYv2c@8^g47)Kq%l@)kn?C1PoJ_ zcgU`I)fGMzk?PRC6?$8MDj==27AP(tQ*ud{>KL}>OvM3;H9i;L0g6k3;w8?cZ2(Ed zM@^|c5YKH<)xrRn-ju5cyyde>05t}H<1E<~9{@F{RL9T~XDSAO1^{{ethBH_==3Vi zvn{laUF0d(#M`2Z0JOxEtIuswiNg3qO@+4f)amP2R?}Q_3EC33R7BPMrWLi96n8I9 zxhCHx+d0~?i#^4)!S-ycvt!qE$~A(ZEun1WOT%4L+C=f2yon>Jon=LX0o;Gd+`s<o(C8{$kt5x4B=x%Af>g(!y3pSSH zmz$a*6>iMOk6w0p>)fgu>zfRbOSuJ+^Gykp-ASP}$~&{%kq+G8NjTkIFK<-*c2mqH zwy&!>c$yjM@f$fy<37oh@~<@I7WU*Eq_(k$R=z2-(Azg!PI zH35pC7YeJ|P^%(dXxn9}gSjDxBxpx!s**qIvYWh|TO3`JqFNHLBD)Eb9 z{+DcSQsu5Y&h=O48Wrt<7WY6`WGQDmWbQ7t51hCKTg#hwlX(BejXQXfBS8K54HoKw zqQ~POZ1g16Ic~gdK)A9R@ev2@>pj*{yHEtVH8V^sX4!b}6<`)$T;e zPn*JSK5Uyf!zZn-S4Or2FgvcWttEY%Gvp-~7k+E7)7+|m;}tsGv5#DPAYY^>AuCN- zPC=8VR=9JWRF_P7K9=?DFlp1qMEk#sx6jxEK``d`fAgAkVuD2KnT&2uz5AR-pDq*2 zac#{SMWXO6?yTMfmP${>7dTS6WmO&XQ#$<>?eq7(hk~DQxD4#}B z*uu#2td;Y)z6`P{XVDRlvqK!3cBw*W8DUF9fhH0r%$iL@CRO1w@v*ZO>Bt@Zb~%>E zlRo4MkP4xtJ+BVudHr;0C_8b~{ubh4M&riOZ)U1vojRGVa^}3s8F4oO z(Q&k$;q+thl##u5Yi2igThu7wmf3EsN7}K26bF=m)GWIO3lCZWjUaI?P z9~poTp03}V(Qeqs_X%tIv7xeu`!%2HHBJRH*91@Vg1=yYd8`64AyD;5!`VdnL}CBv ziDY;lja5-e?bXFYpQBg`nt{|KUj$d*A?hPFa8rXVwK5#Sdp6 z`__8A9x`*Fq(T3~6CTOFk~x*Rbtm zmWkid9j&M+Y&E}$p2xZ6oM*59q|o;yre5Oe`BS<5qft_6dI>L8BB)c+Ywgc{NO^Wr z2v{zSfRg|(GQdWFr@+2_w5W)hx`RjY{857E6cOq3?1dcXkD}>4O`APqCs@!$eddDF z_)1;aW&fv3CE>&fk2@ef2@%IZI;O>oSb}g4e6>=2a;d*RICzRYjsBirCvHQReWWI7 z;yusT$|`oTMbv%M!Ld)%mbZK!KWJD}K;l>9Xc~GyI;ik7{qJC3d3-(VJjy?8E=hfj z1#dn6VWV9vjpyj^o|pr8BTsWtYM3rfAFGxj#==S;(%yn}Hs zl|+@xLja2z9}>VQ}%<3m`r*hj>o875_8poeWBjj=iINN2_r+AAeh_Aj)QesP3mU_Q{Oh z%uyiw_JnFp@G}E*6-SsQVytT00AaWxvdw6{#nDt0P5hls2krhmm6>>``^qEIG0k`7 zGEA_aG22TfLe*gnz=*y+Tkd#y-wGM;|CANNHFbS%4Q~}S zas~iSTlLvVL3z#Kx!bk!$|`quCEi^abz)nqN5qV9E;DKi*dGr<$yYj~+@k#%`y?OU2!8X5SGCllgVH@qe#2bPtrc^Mk1h z_DN=YpjxWM+nKN)gdQOBF54Fzpl|biq0w&9f)HF@y5K{E3AA_a=I062S_n@Xe@uJ! zd)z+4kV*?9f8%HKUG4mhP?|M@7q^0b_TrcHE|Pb5M4{FGV7M-wdvSlY;NkZ;3E3qW zD?*%P(h(<_`OgtS2m9*Hiw%80FzCzA=iBnmen+YofZjCQnf(=bTz`Zpw|=|Nas$XH z-haa@cuLg5eMR%?q@B^w>2PNhg!m&7aUhI+25V;!KWCI9bUK@DtqJ$X8-Bp^VsBqY zV7DbYWy$+aQqd)DB`hEsFH;$q>OP3u9zgyY_|0eZu)p zmTN=B8=@jO@BGU22Vc*3ug^HG32)QvhM3ou=|f-7cCXDi+3mL*o~`Qf%OfzNnGx=G z%3Rl-t2psa#Jz&plZCy#YXd0b6ON;K8)8(Ot>5!{rhCoobAerLw(cE>FqCFC#ICkT zzyI}I_qv%AP=M3;1$_}sKR_=}ADX$^E-BEst9`-XGZ(IiTpTp}3vM;Uv9?5i;Pt%f z`b(~JwFUZpujf?P+2QEn^s0E}3v{_AxpK-vqM~0=H*UF2Qp#oaYWlX#@I~q76yYUz zgE^~A?EbKjcn@T}<<mDTJwg7WGSUUs*<^zS`_>(x z(99<*Eg`8|a~e7>eO89O{voED&U5vy=Y%a&P5Ve(F;CZSp6hP&ve;GVVKZZ6-1RIE z_Rn^vb{y>DMLA|mBT9{Yv)~ABIa$PRINgm*b^-1YDKfco!Ww71*8XQ@pjN%)#t1PtftucpA+`x5fZJ+B1mCp5kSm z8(vs;D#OyX)E0hx(LjRp%BI4k2m>YBjypEY*kFS&Ge~JeAI0~?!v^bk7I;sa}3 zc63{eCE%9U)8m(kNgWwi0@6+0hrw+3=I<+j_!$TuP2Od3CEkg_j;yHy)<^f&Kd0$( zUtqWtD+YJRi+<3#;*Bh+h?OwWmN;tqadzHp%mH^OlCFV1!*tus$N2b6+ALgAFUMvopjK~;9o-D*BxQwDniHNJ{j102TX^dwZ5i#Q%ZG)GvD^4}$xpzX=| z5K`&!=HuB|FYC)|5ZpG^p?6n_E?YVBTV&aql0-8R!rq#3N0iLd)&;f%gO##FDfwHx z`g&y;jdoY=!hrQLjiVNgA!TH8F~pcfOBOq3S?!r0&iBd81n8=UP8D>z%Eh-Wn1L{M z37aL#%*f?(g!?v_(R$xHi^CY3cR{3ro_@evo-K34fp5+p+pXV;n`L<#&eO`Wgn9is z`5;C7Qe$jg`Vp2b-P-j^gJk`JzV|cic)<<|N^5 zQsEm7D4b+2psIQCa>~$?lEltz(7jnkj)89Bq*EPCm3gKULU2_mQmbUY$IlZc+QpNZXbIe`(F}6$?Qu_&2 z!lHGQP82?nzp8=d*|hn*-sxUGM&h9cQ zliJkZ=wA{*i8~z_G|?RK_Vo3095+Yb8MHBrCiNko@-7yATw4gdb4)>U^rQCrvmP?w zWj&27GU;GO3~5!NG4qmf?cMBsJ?#t$!0J9|9pAM&GtC&}NH*9Zva2aEDx@|&Y_XEE zVmtucEh80y)mPXL2zw3+J#Nn$Wp>e5kiYKnL9MupvzkyGbw52xw>{=XkRk3zW{+PL zxC(r;HaXgbF^Al013HXzc~a0<%Zbsw+Pn>T9d!}1X7^NQjywH_sJ%PP{O zZ^<0l#m&|Y0!5#*8MsGxCU2G55F7KqOVQn)9i~Ma2%MFxo>#x|w%d66jFGo4^&ZF( zk3&ykQ9KiP5BOoel+LPshIn!D2C%Y?7sjgbUB{D;y)6w?b^8s`cy^e zeCOS!4T|n*Yd>sZ*hv3V_OkTUmH2rIt<7FnC1w}VotRTx`l^Hp9i^*t!|hi+v?Og_7~!zy-7m~@`eCsHs^c;cPQeA#|!=e(*b7&%Fd3He4HL7Fs| zBoeNyj-Wu`9%%j%D{2C+2-!h?uxU6=>$8L*yT{m^T_Bd=4%RXB%+F9T6ZLOOnQITw>a|w zo2o?Q?%tx@bvrozT2Z-kuhFF}#wI>^G!Ds_4f?B+ ztRUBF%9PD*zbJ2DAzGj}sOe0i^AyLGQeSnso*cSCWj2q&7)ygIedeN{L-UAe8lf0n zzQ7KMQm2|k+Wyg|S68ePGwl(a?0)_TTEA#97T$a;H#tipH9Y}VOmlYwSG3**FO-_q znUiaCdmw#Lk19sXBN4+grE}X)`~%56eF^g`C}=$##I5|x$FIQ}XJB5U9)&L+avQ2Z zjulVAoUuat$Uc&Q`gkz<=Ft48N3Nsm;!0ZMV{U->u>83t&LZ)9&)hx=W=(rdt&haW z7*$@T6%f+XvcBFw=!ALpXhjmpJ)hre0ht?#u9`|I5N*A7C)G`dBHEidh>^=9KLF~5 zatT70pZ_e#LKMY{j0~L-pp!R##|c2y?;yVsjV7r0nq-!bc$@BX=R@3ShBfq#)E#+7 z<1%kQ@cxNbqzoJrab2QvwI1srl;bHZK^&ip7DU(0gISlswv0yHiyW;ROX$CY*Qzxf zz@_5Z7RPUmdZt!#11kZ`PM~mUB@a1Z{awe+g;NWbENrg?<@5JIH)a<&=AmV zC-`SENY>SjE^w|-O+Prs7bi~Vi9_T-f58A_eMmE1s&-~d(5ZR81o^lgA&CrbDf&dl z9PYLG3ABI!fAQk?w1XbEk9M~DH}{xmU#&MfwL!>Bu1&T`=uPffO=}j8KBO!+cAi6# z&RyKn6@>1iBao`dydyW;YaD~b$P>oMN#Nn{lTXmu&Sl@fo(#Rj(#80iH6Si)xR{f& za=4g*^Ku>KgDi%RGx-WQ-V6B(*y~Y-Z#N``N}>dZZA!@mhY25R-sj>nkho)~k>OLm zp+Vq{yCa%hz9H329fdM%DME%aY*k9gGumTSN=7+MVT)D)Jg-``WscP_5J z7C7Y7V)|Roc;q`VaMCn-EaZ#gcr5tmZ*y{0j_iCn(0`cmP@t*v3&|SW;k2k6+hM>L zX?(CyAc_;U5b)56&RnFWPbyd>6e=%DzRG_cb|UUI#5(35r%$Ogie_ z7J;rp6l(`PVPZytDb32G<0bA*Po6}ody(WRgv)FWO~xjW1*_8X;ZGbG^Mq5v4ETSt zHN(e|b(cX$cb^l=Z|W5dFHj_0$b8EH?xD1Yg=S-4B$3FBRZeK|Jbf8z$id<1fmwsj zPH4>S%}65AbV4FLvS#*Cc^dHh7E7zh-4%r-mm?GLl3PESVW`Ski}m^?kk6RexpL>W zOIMMwm6LLieBHFpA;t{WArJC-5Vu<$o3o|DOp)UMD%QzmL+l)H!rM{FG0=!vG-l zB%N_uWcd5d7G1fsybqkQ%Yk!&%WzMdhN&FEZUzjF&{)(s+_vNWp?SddI``8=Nj9wH zlelS&yt5GHwr*C!Kt;U@bT3Q^9!d7f@Dmrzw0iomgU1EFvjorn@(EJgvrh8IBfq~; z&pBI-hg$PR^NNJYCslh+;yz>K#p0uQDqqBC4y*&mr+Y!d|Jsp@Din&+CQ!5FftU6_e&@ipwG17&$wqo$)RHO#By7EKmA9JbpIikM>pqljo2>!jN({ zwSKK+(0(9+{-Vn3EENi6X=7ru)3R}P(!i_yPt7XBd{MyM72tS^4S}pjm#rxufzWp=Tb5T--PL~c}oRUH6j7%tS7B#6R1mjGg4gMST8jr@{C`ck!tFxro(*V53(1LcR6bKa~7U%aek|!#`T> zggSBd4AYOsurt*kj$z)hhiqX;bYab6F4D#?0)4!h2p>I^c8wgmiXpH;y_#W?YPcYJ zI<9}+WeS-jS&oufJc7x{<9HSC-8=*E;FfgL305WSN)uq>$9yXY4$pyLqcW0^XNo6O z5(X;aa3^CctCK;L-TSZ;#%VDdO*V82s0RMb>tq4ptctTE%ZUc-hpGR3c;cE{X7aSh ziFd&FnLx zT9HF4WI{$1Z9Q!l5q`0H0=~NPfKtivL{J3lVxlz$E*f+P-JybM44B6Yw zrVeL*uyHD#FA&T z&x;h3mt2DA9=#Ds;zc>VwFX5>4<(IptSy#__$1f6zxvM~_{3C^$$O6dhq|ydgLvuJiK%+*g2(Kx!!07IuQ(t3X!us$v0XxBnE_TSX=LB zQ^G7f+<|ip=9;-TQzMLO-(Fxys3)Ri{CxuE4Hp;lq|#zNG^9f-4^x&9BlnM!UIPaf57HD#CPY3J+%k! z;@l?`U_2%ueB=jfZcX@WsNOr-6d0^B^bedZWa!;@2YC*)d~?m%qF6^LMjINH@O9(@ zMXtFZ4L(e2v1omS*fGb@)3%vvA7rDAPVvQVZI+KpYUmHyljv*REyT?gb4MjZUyv#i z^*F0Buv!&Gp72fB{WPjA9LJC7#@CmGl9s-d3HT^Rhm4qYY(>J1%VWFJc1w=V_#-h` zh3{_JxglE@l&n@>x6k>8zzf1D*tM_w)UI`6_E6L(714w4ZnqvQ3JeUsX5)DLQp_oX z`Ui*p<5(4Nr+ZE2Ntj_?NH816``v~BGyxe|Dcwg*U0kzo=5bTBfV;{qv0q=U_om=$ z(I0lbF3(9$=52}MU<_6|eNn|*Z|H6RG*MW^CqZM7$T#|x!^g&v)#uT|bxj5V0UoV+ zAyl1OO&=TCzK4vATFTksrWX71)&ZV&=^ zkzJFmVHR#?tvk4IGH10ghA2_*vt|wq->wS_RDG=zs)MEz_R_fZiL|F0A+V&H@_qFC z7tO@H4DW&nZZaLa?7X=+gq0UULn>i4ael>HY9bH|S@uoXU1Fx=nC`OP9fWjZ-ktE( z+|ZAtAVrKgQovtzT~Y&Hiq)xR9K(p4BYV1mvzy1!s`rEk;YEh^0cQFjzN+d{ZHBIteA$HGUgS^HCTo?>@*7aZ^VF$r28emK922_UP=@%%0iVzA3I$@W*{~D1W7Wccy4gDS5&p8 y@&08ltInh8p_RmzM3!z{rixf%Wc0H#S5PAs^r1##NR4Ji%q$(gFC@3JkNf8Z5S9-68CWweAs30f^2nbR| z5CKJcu~JkJ;X47*SnlU}pYnd+b^ZVMqB&=GW@l%AJ2P9(32Z;xo&XTKnmU>Q1OfmM z@CVo)1=Ii%0zx7}0umx3B2p3(G6Vx9f`S6UMoUM{z{A1E$HT$RB_O6KDIhE-!o@A6 zEhVR{qOPINFR5pwt9npTOtV3HRf$7({@!c(W1HS)zZd_@CG?j1uP3mzAnGpfZ}9zA zpP+2=8H?h7Vnv>$31Wwf|+!YM=5EBMokMYdk+dq5KOPWozYXV{5n5|!e6<$e$y#7q0saG!PJjuL?c{t`+55; zj>>ep71;%RME&a!|5jtfwQUV7g!CQvf%J|9u};z@D&)=Q!hrSRdi79(sd;}>$S z+c$6Cx@k#pb8;|^5!Qb5w}$`q0{pHMGG$;j^7D+D5#Zg(qwq1SB)-hKBcwXdUBjo)T>K$@4u;_mZfG!t$ZWoAjY%KVI~lsz-TKVMd6`P*aG)|BUmYt+`8_zfCiw@$gXaW zSDJVFgil|6=7yPlacu(f_$-dlFa@Mn1SJGBkntXX_trTR2YLqqFQ~Rh$mqZ)<}AHssZvI+Oj*_12NrGw)Kayxx$!CpYENe(KFg%jR4Pb#H?6 zs%=3+x$D=g_l;81i@~-RcQwE9fY#_-?B}%pyy8^!+xr>AMY9WiD^lcL^NRA9$nPniH-CKX@jH#D zFE5lbxyil_Vhm`^U%r?(%E_ko;|q!^J~!IBWblSF`yu_ z$hhIEj#FP5TxSyUyf=bGWr_iO+s(Tox8Fovb;#A~SwEnRS@Rw{s#O07y%5YeDIq=f zQZpc1yk~Xh%KN1v`qPdxpYH+nM zpL;JYFKyPk-#y^>LNVa3-JTx*@fETVqC~!QE)^6+dhbZ(RA%#N;l&#(?jAl`a~~m6 z;~}Eg6D9)huzby_j9*-K*|b`>6}?oup=?Wgi7ex~BVCVAN|8bDaP6np*l`f|ovQfo z3Ny0+_S`qbZEePP^Q-ksUd^uIO9w|*9o)KXriVMd6h8Vj{l|*$34m3uE!QV_(Cy^g z+fSx?Z{@eip~1L<)jf4Guu7p8%>9>Bb;-+~=Wmv=QsH$O;&Y33r(R$)^LIyU#=crS zx5H-sPJbitHv)en@HYZD2tf5ykT}`@`YrtAK)}&#AM}4+Fpj{Aje{gBhMqVj|Mgp% zOFFO4I{z0}SHEir)r-LVUixS;Ti+=qPPw4i%n*saH@hZT#X%OcfcuPd-{6# z1e#v<$g>gal0+N=!{+8^=>4eV-MZ0SqyK zfMY-=FnKd8osbRyZ_G~LXfgt91OWQ`6`w-fCa}bBiK&#N%L5!Jh|ht6(;;ko#o(3c z9cOREY+K!Y8x%$;VnXo0 zY9bPir5vVMzAtDs11cKq_FAd*IT{(k#=lbq1GZQ2X!BEs&E&ALFF@Y4c<+uR21~+H z!2c0!f-3DR^MUdxY~gMq?G8K)kPS!m5BP*Qz}esc1W63U@&Wq*jIl<(*Q$co4yq@d z$A^~h9s`V^;IWb`0LNf!%euGIQn>HjVlic8_3z1OT)4gF+)iHr-4*axHeIT_iQcOO05Fw~ z{=(_S9|Zu`R{$AXRxO?GwM1kD9vlz=)rkdFg(HoBGXMkIc@g(#Onk-O#CPdP(^ zOE#0uSZhrf+^9T>+9QGD8OROuXnAqs39h-874rcR032n6XN&@>;Lup1UvWi1(Pc&; zu7EIB<_}yCh44nKm&yWIn{64rr1x@cw@qSgE&zzYoH}F;(E8gpF6@MvJ*syDv;4b#QnV=GwNg>PkJw&0 zc-^m%7bJ41>PN{>7R!dUSkMg0z?g*j*ms6aKdp6sYue`ENo^aKDU0t>-UEEg$8DWKgTg~&3E=Zf zw!Y=6es5gealHx+fq)8P_@I0kWa2My?gjwRP{9B|>GW#=&>7O=M(qy!ouY5b?luWb z-al!+>rw4Ct3P2a=T3g_bUZdrUEc_~Bl(RVdx5DvYSHRy#VL6_oaPCP2ZS*5$_zqK zpzjkFf`bmxwt$A(UB)p_-?vM0-^>%Z4M6*_B7T_Xx2@NAMNi_h+RM!)Lmh!;l0Mg_1@sjwlm!Fa|6cpa6B_>O8_D3DA9grTqljI{(57t?hT zvU72}8dqQ6j|M<0)_Bm`09pqR?G$J+zwY@%YXApcFeadj`4D^<5MhWNF+40sVQM~L zikBpq0eJkR{@RRhD*y%t0GzMDa0VvN@puuIaqyOgxrFJz*f7~SO=E+Ex|6V zH@NLOzx>PMpD5|CrmlY|7ePaYLqrb$WCa>s*-F-X62R+EwhFLgZ50mMDunnKBiM~m zc!?Nz6@X{;tD2yoyJmpo*Z71w6pnD&#_aT}o#+HAh`}K+-621<=>pDCelPXhaV-Y_ zLfZZ_O+Pq)^7;sN6=GF14FpB00Tc^CP*tYCO;KnhXrh1_9|;O8uL{_Q#)|mkTA= zK>~%Bh~aD(f9f+MRzOWn9MtQ?ru<1U@YxPJIO1c=HEgHs8(3fuwsAK5On z;Mesv&L#=;u*_A(Tx`GglI+X8kv)VP3vd@7kjGv=!|M`QbuCweDschDPsmD7$-%J= zTvNaSEiWpp4!4W{=1A|{z)^;fD${Gx_d>E`6zpSk3w;F<2hN|`y1h3>WbN+k4 zUZ3~Fp#VNiHrd+%k2x(0SW>!R@VE(w*uNq<;Nyc+@N=vJC#;>tDg^b%b@lGF3YzRM z>pyTDz*S1`+N(Z)KQv&1L<8XXgNX`7z`Qbg&Hj`KKvK_ZzRG|Cz|jX2mWqtv%$#y- zT6VdaH6AS%e&I09t`%%(& zw#*bKjdsNDeqa@S^FvV_3Luv+GJY;Y9_v=;)aHJ6eU;RO|IT$6>~a0ZVLr?Aj1iEKs0|geD{5fYraX_&8w8hDS2rkx1DnwRJ8D-!JU#|P z_i@n!PCxYh30o*K0^Cv?Apvkh5<#pQ>hu?@Rbe!=3@hUMY85<^%%`2r-JSklTniyM z*%JYF9tHX*_yaivO2q2Xhq(osis3ZU}9BDbns@6_>qfXWc|3u!nhu~!}T4-Q^!X{`e zRst;YwU_6|-JLM@7$tA)*R zvkN#LKL8X1&_tSUudfy?HpwdF!JiX{yb9uS8iTNQMG+lyG57LO{svTt;P`IRF&ZnU> z&jm#Q@~e%nuJhrL$aoL*U!Hr1QvyF>TT#q-oua6Cb_0Y8fg!{gfpZcZ4HWL~u|s)I zRFwY&>b@qx3M=6UsAV6|*a{~Cco{nQr>`E`2ChwG<1BbC1i}mO0ccFTa>Uy^k*azT zsQP?Gt>9(^6nkJq3}8JQM9_MebvShPIlQ@wy!75gT`B+vm;H!h5P9%m2wm(fw-qO& z+=rtzB$zQJ;+xi?C_H{*z--56EdV1SHg>E1zz{AwjyA6*F1fFgm`8eu{5H0#P_L=RmBkxG@ossOo$xynBeH zfrs_x-Qyu4U~jXHhHtuS(*Arr1e9Zi{(g)BkVKp~ebw;GwJDrNfoWMAk^l#wqSmID zYDe9u3V3wrmwWjFUZ}m0zelc#1MnV095f4 z@4*VmxGo$Sx9efpP1(NRj3MQ2JQW&%aJMKwEj{=2!}s>UkW0YcGC$hF2GkzyWhtub z*&%^`j7{VoDQ}uq+yUaQDC6ymJPuXtt zzuCpTQ|SN2HvVtCXHfP@K{Vpz|6(Wp7X3E@e#VyzLdeE+M~jDqnsr{mE5heXZ8mOW&`Z4d)}uDnduc?XSK(#)zl&=(7B7{zvGo`QNY?E8- zR%}n%J7-BM9!}#!qAOH=YKOWStm8&G3cHwG_^J6aTP7e>FLe0@n;}E^6%A9Hu`rTl zMP+tDjVbsw^+heoB)JWu94W?J*PJFVvQAMgTa&=VDu+U%a-!I@3J!=-0Pv7!`lE-Y zW5$EU*YOz&L?*nlANbUxQWmuiXwzz7*wB;a9V={sjQQtmL3KWcnebD>PYP+6!5BIy z6l$p$n3XP}`5uHmB>%jINX+0&>F+w*lsWg@RAR3h;}WdwK{;Z&(#+-|AK>s@N1Q@A`AamCEw(EDIKWk|gGJ@=zy z2_KU8xyMdsRwN-eBtswd5U*bzqSRj3O6)j%Kt;ZCEJY|qfryLnTPtk zCcR%vUGsP+O;eA>_S2j1(=a-r`_xEX|8AC_YwJ>)CEhJB6~Jcw(HkueuCWU3qm_hm zI{wfzT89d_4mvzOx%`mszCKb%KP6aOsRen=PuS|9w4dC!p7~Qstl?;l|Ik1d6Z0 z1HC6gY97-t<+C%D_o-VI25y$HvBTt1N@8`79O1e?+dxIP$+aZ>V#;@syry;L40a@q z)^SOX^h29U2#=;aI|-UlWoYqK=aL|gUFx_(g&CnSeEQP+9*TFIu5^|D8uy9Q!qMuN zW2X6EY~e>x%j?oh5p&7m=^l$0L&WRN%N`t}&+QUJtw9c~v<^5679~&EOg@ry)s0q- zf~K+$3*BrwRLpId1~x#1Jelf@{OT@J^^NW~?(Xv0XJE zbgzR;nTRD$GdYTpsZw@mo|YNYmGgU@bu!qOuUWm~0Wv=XG^rDcjRnj@MfpY&9=6%ISC#Vmux@d9f878G&7tFa4fR+Ydhw|0e z?G*1Su7L!)K5>P^J`hgp4b6P**aql1E*T6a8_V<>F=UicYx~i)m=Q)tN5>tLkt07_ zzI^yGMND}U*Lwx;7C9?RO~_4{@k{_?*t~JNe$Yy_&5{JB{}~r?GwKlDub8#2^igS+h^>v&Wp?afW043(eH~S`w9e z3~mPNim|Q6lMJ=x}^w6fy@THt*pJcNa(zW0eNV{!^26)CfF0&S! zT5b%+pM5ovs{x25L0uC?5VQI-a->{xnpNOX!I02^z^}9}YiA||b=)S~1#dRl>zm74 zkY9SNjcHuQ4qCvlcu3*h??Incf5n9E3zSUHrn4v)9~0*Sh}4OF==(?6s5`tEBe7#3@)JEUIdhNz^Ud%6wW4Vdv6 z&d7;5L)kIX#GWSu;|m8U&plA*monpa45=h)pV*LxAjc#H2t%$crilDhJwzC*@T?t|9X4_)`2 z`CcA+$H{V;py3KrZP2sfF`^Gwm|{03&iN`+wF^FCT4nBdEs&W#1gF=Y@2|7cok_~( zmWf7i_}@JGfo7o5lP4D$g(ngBlwIPY3h<$v)R;Z|GWgqc%24jhTuG1A4>8|~qOxSu z5_U=C-(Mw-fW_TFBM=qri&`3!3j#EJ8l9i{U0G6L?o zo=d*ndYIvIRNUcX3x_I`B?OD@`8!yX>0y0cT5VB$CXLk+p6D2Tz?COEWhyBspW&p zq$)p!Nv(qbt(;;3`P{pH|2Gh>YwWPxub(yxkg@LJk-CcxwGrBh>IJtw?pjzLudEv~ zdRpc~^jN<2Sj0gcZKo+MOwbS%dTKp1UhZH4dC3#*IZ^~orU-|hRH0X$Rx!1J>gQ|R z^F$v9zr;jVlWLc^6-%5Ln3lghU1m_>6%*BZ?2=)Za*4{i(yb-i9BPVc0Ly0KDy(#m$KT*Zo1oK$V(Mtp2B&X>C!>^lV6X1 zDUCm@7#LWa@qjE>sK43$NSQ;rc9S<#Z6|y5vB>31BH0|8)hS6A#v615$usqM<`a9Y zYR)816Nd|glFq+Qip5;jQA3)MYH`3}8hoYRy7Dng*712?7&#RDkDZe{t(<*bB8W|( zT=qOA>=}e9CTOg&j&+L5yM56lKj?HI{^Cg`zHQ*X-05y^#>MgJRL(i}>zlKU8OgWcRQS<}4Czk(sfPXf<7qSHvf z?~o~HcfzpTD49l`y0hA%umpj@xjfE65;oaX!lKk>9qlB(>HyS{S~FC+TDBCse_44$ zR#>?kJ@havnCoJ@RK_ilMRmf`&C5y8iFJ$V3ARWMtW(YXPl6$wK=3vtlGd1=>6jW( zfa*JDIX;@xf&xYiP2^rugDmo{#scJrwDC?M*juaW)5fBSm&(c_+2z8=+zY=h@`pJq z*ht`WB&h&|mX3#I%Wg&)O zvNjqCH3i`Qd-Hr z^;YxSu{r=gR$hkBCf7Q4PPi{*9?io;f0+rY&TPKB{MwkPqwikKR{ zIP7^pv?l%7w4lM2#UYmqZTNBBFe*4JEL{HB!e!6UCuGN1TQ=WHr!@M89#*N%lRU=a zsTwzA;N|YnzJPL}iu%CtiXN}usDdHdBN|VXD~kpGf~^GFSsv~xgRni=FKcgL~hJ~o{rFreLY3(tBv!Q zE%DWhE8gSaq`z8c5FBFDY~QbAq8hDifj&-g2-e19I_tfrLA^3)CZ3w8Yd!kzmKn@~ zkd#vxB zGLnJYI@!f3=11ERY7CkWMjnV$b9jx$JWe7CJ0S;9`@B|aq%(@t2iHPWnMFqEFZg^Z zCwN^%Dl|J!MF=*v5wRtp3fcSD_6 zuQSQIce?ipUNP%V_MW03JTqu@z_>QIbe7CKor?;Y!=)tx?&MXg_$ImZ`AK*Lh|RMc zVRTf8k~s39G!3)!wzB?@QfJEOTx7{rOWF|Yg%vi;ck48mf?AHwYLKsamA?DjJ|NlM z<@dt#RO;xRr(-7Ee)t2!5*>*%v3=i8l6J4vXe;oM_G@U;8!D1BK`3# zZeQ-R$69?}ytk1_rc-iJD$~7RX3n=`UO*}jJeV0Wj3j#hzo5tq%@Ww(-c+U$WDZuC>=7zGv3!! z^VnRPOUEMMp}-mcix951qDra5WiaMJ*1N7@(Hu9Tv$If?T1Q}4?y|WiQz)F%&MTkc z_eS?i<{jS#*nFTj$d1!i+JB|K0K;CWKH~G;I9lWDu+Q}d!qh1Yz>jr$2IpeN7h^4A zVP8*DyLSXMJINr;z&iK(rEc$>j~n&jEgAaOdg(L>X@$agPBj;+XCuwTJo1l>!w~sh zw2ypM3X#mKntR}%^conb~+-CUG!)482>Q+AoINWwwo21_j)v)VR51x)g(ZKys>E{u+OD(4jnL;Bt-^=$kY zqd!AmFespt8-{|g_?o)j&L>#cAQpo~aSfs9F1J#0nk%AtNQv3(h;A<*gHZD>=;eHs zFP4k3pk3!Ixp*K$T5lW3TsTfX)1ABx%*tD|)cH6xuE(sdRXmDji`fVBX?mIqtyIoJ z$~?V&sc}lC*f}SC>3+j7f1}6MT74v6%UVv@t;?Cwhw#MWJa1~Lm=<4AFJ&8KCv%$0 zkQlhj-h@>CfY<)q!u!z^<14<%gRRQph)6l`o##wH;4sp$K60Rn6HOJJt8Wr75pI%_ zx)K32`j+rLqvVM}T*u*|Fl~xsF9qP*N-*rbU&A|{{kv~RsT6|b&D#N(kZWZ7= zdqeP8?u|qaD5I9rjhLy4bK=Iw*6cUK-xH=XmFqr_2`-FUha1Z{)XS8IJ!H6OPnGk` zj?O@`+>^sW{IM*$z~Ui&8gm$d`MHVm#?uZQIgT~&l{>kvUqACes-2T-3r6vrQ}URD zz8zJrAbZO*r3X<1QIF%<3h~ALJ7`kv_r)KUk>WziZo%n}YiJ9_tyRYymS*RhR#6`a ztTpp4<753Cqge2U&?s8}8IgCtMQt;W$>EDgh9?p+!AQ142$RefW0bldo-W_y+xGm| zq+!ZiE!6u2^~UMp4Z0ro=Rt2IK`=-Uzo$lzvTBd`c4X{tF$xc{XRh67KHypRKttcI z&LAGYSTORDHtZyTUjPZ`9hf-+ZSz!LOMKSMQyKfwu09N|A=em1m^KiqAyN2&T^1<- zOQy{4q9J}TLegky_r%%7KA(Mq;Xb8A314EECL6EPNEn7t?r}luoLFWXL#rlVyZ!C2 zFA)zG5Y<_=uJd8Ou|cuNri8fV)5~ejx3-)KY_Jb}eFH&Bt0B9{yP`guD6m-=#>USk z9p|l~pm4j!9AXS%9jr=SJXGgXyh*Vs`(SqYauWx+F}S_j3{cb6eR^7pzZIcgvW2fC zNovx5EuX~WYK6HR#%1xVh|b(n7n;fl?jRjf@;;oO^oHHiL-9dRLsrHJ4r)6smGf_{~wf%zLWV-CBlZ#)Pb8@tCNd z__fKNv@Wwr`_#n9%`09GrU1_ z4bOM|`l_H6;lj$%`9n37Pi8OmU320r?LJpv)u(kZ`<&`6O#G$&TM?&JcB_SbuU7g~ zwMMWSd(au(sCl|03I?SVCzgE1`7Q8uNH&R&>hTq8i)9o)o9DCV_KP-uLrIreCwQkz zmR}uHa1~(P8ky=kni%GD)7vCfkP>+jfUGYkSQ$*j+AQYIjnRH4Seap|$$5aRe=65o zEBc;du2a!jCXD}dxz8ofqZ6#X1>SXN)L|o(7drRc=QrAbIU9MO27i$^lJD_|gG2K4 zQ%>%SYDrfB6zcrCiqS(g8d5XyfAyjiTM!#5Kv#C`U%i}d{;4a=AxVq+CCt!5N24&c zV|3=?+LLO>Nu_yb4`b9p1?>l*v6q(PMGb$dL{wK-@Y#tHKp^PATIP}H6@5-XD#9Lf z_+X|~Bzud#i>wM+4;l28@_p9I2(@w{jKe|tl_p!``JSBNq6_iaX9MR-Gj5*3~^p#&Nn5a^x zlIv{1S$2$V!D{hbhLBQczCkl%E(5&TFG@i?Qi1D6Tx)`bnLONKeP&aQFiXXY(|nTv zm5cF?CZ#JhB_uzuRxaGy`!Snerj2Jz++^iKQ_@PSlTOWe{nwpv#~qo++F3% zp*C!m;(S}@3D^>wO^=h}*YVs90jt?ZO5TI*v}dKR-{MNNx-{kJROW`DUr0v+9a4>Vm|P zH2S78uU_}@`DrxqhZk9>sFTt}h6Y86z0|f&x;xDE3_^4%-v+u)?is@G6{)QGv&|{w z0t}eTbMPQ|^SLw1N3I-aLU#91w4*97Dcp(_yQ`0Rp#9R~h#!5VamG}$e+z#VT(<4~M*H}h>>@%wa;4>@Qyvv}( zo+qHhIp?yE1HuyTdNOSYr(l} zsBpCg)FX(&MX~IjuoT1K?jtchtoRZvzxV7fA^2ZsYKYCf^VG%0y5 zvCykj=&4;p`parN&sK3P?*9p6EUJ57q}Kd-A^dwll$kYyV4iRt#(Xj1u8h@m4!*&2 zr+@@VpK1J{&n(3=(?<^y{iEIE!~F7u@4L%TwaE%qw<==aQU**98Bc3&eDa_I@2BIoCM zEeA$tr@VxYhjl4vJr-q#YacK^2z_6xk7;(x;jU%cPbSdmHsO(uRx1}Fqv=T>>l0Yh z8fU!bZE&9F&3*+f^gMsNi`)u9KcZnO)Gy9hdivt>h-v$$*GT*_X4g>%nW~h()cyRg zx>x+B`xfjs-8Z;`20ZXg-kHrQO*A&ig09zb041ZPBHxqtk_8u<|AF{FK=TLj!Rr4_ z@vX$(nIT~?>K^myYI@F({1Rsgyhqeg>O>bGzGyzki?8w=a#x;c$hY$IhuU=U2PgB{ zXKCZZE`2P_hrja7QVAcG(espbVla)n6MEkYLS9z8|FHZKAJo;StiJBp5Svlc_kjh5 zDP=C3d(2u@3O>tu1B^4{Ey*1|6EFrk`psU%3Q4=l!7m0sk28PUXwysKpGFWo&bM$UkrI_0rvILLosI7qxUfb=*; zomBB-dXW#l2s|VG&is<|2z%b&RqyHhRrM0AiSoC-{IlwMxL#DKb&cn}hRB+s&s&mB zSa`l6X;cl&*O$6t#^%G02lO7;@c`vFQVJx^uO!YB-F8o(@a)@P#CQ&NrUuGv?CDhL zdwVwYaJka__^D)9dKu?}Nd*EG!vn^>nMyIZ%V%<`Kv@Z<%^kZj|7MmeHD^GNHQjn* z+SGuJS2-sZ+zH5Fy5e`<%XFdGpVH#T&gr>3(F7H}jT@@td643t&BEE_i=4W}w0?d* z83EVjjK!;c#E!yGv{t213^28y}T>=Pp z=$q$uAhBionx9&gKb_axHs0Sbhc#(+9^e*Rh5< z{=+bzU=1_nhhhGsh{idT62OrWR4Vrgz6HU0=0EGf`d$w<|9J%dLzr2454!2xo}1S6 z^zYbu{SP-S#o9XW@3#Jj8RK_bPf#Z7?=$pCJKY}FdLy1h;SKXLXpAogOq*n}J}cAx zWQ+`^bK}m}Ut+)?gkz_pq#b+f*|Rt2ydtc<<$p}hhwQk{efRtcCkBJx&22YG{bp{Q zJLZBMSiMDeNRqHluBo?;`<4P8gO;{71}>c5Id6_%UQ-EOzxb(y9CCV z<4pUqo*$S2E#Y2qDcTb|w|V50-USWexZ0(s_@415 z{*rZy-J^N~3d#w2Vz1}%XCAC-|FRAF9ca$qq!)nF_bdP6G=GXANlQ8nlzR6}bH9Xb zgWrAz11wkXdC<#$z%IW7`^tCNH^M)up$4p-&c6HSD*zyx%>E*K&`BL;wsMjlraT&L zE;%VQPAjjz#n~tSs1;XF#4Nek_r(i1zI9DS-K_4UKby>DC2+PlvoprT;24K9M8Pln z#;*s=^Upc+PcVr9gJN{TyV~nJ1u)KFun&a7dkBOBkl&|EPlRbc4Y^p;Pl5I$2SZg; z^2rC-A@u~K>D{b*nPig1r@O!@^MJ-d;FWLWRd}OO{=5@|P8`MfavwjFw#x(_o*+rh z55dG7#t=gGrkM(npGQ{*XMbDc7#`;E0dF5X(WSijntdBy3#7~rzp9>lcSx{cp^Ym< zYI>4ig3B_6H#7>cNC=sQIr?SULErskTy0UIHPn6rEOwKOHKuzj08e96a3}2kbE`ku zR0xF3eB=jmOwc$rh}Rx;PFE(F#Gu4d5kp^9gF&<6*{|$>Ud1rbg~{GK_T;z8DW2L- zX<{pb3wJ^Jb2w9qkPH^l@tFYQ*_14}?3hS=YVDl2W4NcE^S&pi<7S9lI$NOF*S9}~ zffe+MPJ2Ey_F!X(a?dwDX_3*$&1|av+zLm+{wBMRWL^?8wtnJv3$YcM5T-(3brePP zgM0I_QsFplzCt{WFnS`dhYcg|YRte!b#g|$$afk|z&2VyiG}LeUbjAz)01*Z*;7eE zE^8B>`o4g=nd(B9JXZi(LmSo_nh8%x)$RDmyPAU_Rvj*{euz}yMzQ}KC*D1+ccuk7Uu;6BPDX(@Zf1SbBam>TArSD4nbLJ}1XBi$r%F|s!nL}#1>Pp=L-kLpui_xJjbd97RyB&^gN{&ip zVsB4~wkTFxUU=W@I#$#SCp+nub)q?-YF?=#(ws(#Zy7gUJmP4oh*HG@tj?;X6 zafRE!hv!Drbb$Cp<>|JhD!clp4@&#llF}4)zmWX1gML-uinyJ6qaM;ak0*~G*pB&m$O;HsYNs8DJZ zI~tQ49GrD%q)vJ-?`(i~g(m`iaN^hY0l z%;yjfSiDo_XTQj7LHKa0e6;-#6fB{gXz(NHqAI8F%{VVef@|+-zrYhg~ z9K&YzC-#}OHqOt~%v7!7||^i=}c|0JkPCX`n<$ps$plPg(b-cs@|9J zJjHt+6xPQ_IT{j)ttFP|Px zsyb|(X(CmZv@47bwmrOT|NIc^%S_Q^dlSum^3;#a{IBIrpZS(nEgZBT$Zsi8jM5GM zUM$T06jK|~%BUOU3-WQSAAI+i_K{cvcjmpJJGVRTn|7#kcJ*_pY&~$TeX)GERkdL< zDETPL$ygB6wMdP(aj(GBqsVMRU9jtU65BTLF{0*n&rI4gO2Xw;ZCTo`=g1VDvBJB9 zY6|zR5a4b2X-h~PNSMyF<#=qnWQ-NxzVQCh!dLUNun%!JLwMK3Y%eE_^H3wZVk8@c z9_dPQ)S}+=u577Dj~=djX-cxjbHSNDvgo`C*$L=TH6L7^RwAXdHS4#IUt?Mxh(bQHqf`E>wF^OOcGhE<%Mw<=@FVo@eqg0 zg!P1FyksY8LOh5aTitcisuYO8KUtBqD9Vtr;aG5F;T>c2{;MV|g(KWADksv5lag2p zglex{k)-N9=bT`u7QM)w)~aagtsms$Q0noT^M&;2g0SJB!X%Zm&X^|K=u$wqNqTw$ z=Kf{w7h6xZfdZjVA1{3+g!&tQC!AQ=XGtZE0|Ad z4=68+Y7p341>7%LC%^3`)OW5bbmHLLW^}Ve$e|5Ock#uXWL11ht}rjhz)XL+%L%2X ztb?PkkFoHJW+m&XEM}K3D~ftY2Nk#}u;g`rrcg*X9W3~2P}!z@<<_(IHR*b~3m4sH zH%zpa)s^W_h#oO6Nm9r(UU+*&(F)Era3alx5pDH+NjMl@bNdrbQ*^HiG@{^YI_KlK z6~rz^^h6sE2lKRrZ(-gVTQfv?J*nfOa}<&p;K=`a(m(?fB_VC%_$*va*R5p{FU5FF z6`yWI=ZaP4nt|y$;`-SO;n$zZ4;=L(e9d1#)_6f$N zH2(5N)GH>Z)rR^oPH_t0j?&c)L|~f4{j@Vl6m6EVI$7~4%skYqf*f)b#GIBUK^Nar zl2cbQbcio1au2-1YZJTfndNY=m1PZ?WIa6iAS>&|F@jL=cXE;+SRFT^#poXq_ylhg zyW|-YZ~AcHQh_BAo$EGGa@2(Ag1uuclPTdR+6ft!h>DJ_a7C3txhH+6N5~j1pBp%5 zLKts<^j^~xov7!zK@XAi#;Ja>$c;^&pTNa1HP&dlMwaz`GZJE19#Op6*6#e!K z$&ECw#*F-${rTKR1Lr#RKJzcBb2bu}#VF5kZz9$TDaRwsOb)DgJQ9sauo5^3Eh|2K zBHh?TZ{2=LQ|EcP#6hw02p!Q0txCEK>jk4k2W<&1dMeTweD(7jDoJb||5kxerE$`6XReYYRfasJjzU)Ge+{fb(07wVke9_c{p=`!Q7_QV&>A5_+aP1i9jwa2B? z@NV6*I65Uyt>Bn$>B%(6D|0E=b(!~(QBhq2`@M+C^NUucE5E8KbaUb-Jz3O(|Ey>F zQA2A2DWdMVIYxz;Wi!4P$2&ai127x$S2p`7ti^M5!>pcHhb!3=E^rULmIOoOOcY!MtVogBeo<9B-<<< zGhp4SK7s3Py&PxKh-EUD8K2qmDHXl>P0KE!S{ADxX#TaD>6ZJfDTKkE$K|W93m`qj zr)8=dj6&2#f5q|*CeX>yLtz(lIV|QXYZPw34x{Mpu$cn6h7Z-)PG7`!nucIdu?99w zdSU=my4R1RZtyO!8&fBflyoEL9|w}X79c%$`xo0uw{(2q-Ix0!V8_TbeLz%*&$GVy zgEd&!@XF*%*I~^jw-huRO#2rnf=asX5*1;$L$sfJ!h7i*)2(TDdRd9>J(Oe+Ri_#pE0Vk+?P@N;nuw$OsD_U z$6YQgUKeJtc;SP;pY|7b1MPU-NpCFR`HMZSw13RCpATG#X%1l8gMR~JJcFQ1)pn|F zXm!0nV>w?{dPkJ8{jS^Y+l4EnhES6FwDol*BZ;~PxY(J-vZTJs#X`sUd;6>1H2sXnnt-^s{E9_F9sbT&F3 zQo~Ji11s#6#QovD5#r_EX|t=FSTJ>&vZhS@{&V`YBrMmYG5ALAPMEma6+XR#ePCdL zhWC#8GNbAeJBm$7)GF_UC(%+7>g4cy?Q!>JK>a&*^ndAw=={J4hMY>Z&6BDpFW!Gn z`<;b)m!hs_(bL!|Fwa#{GN~X`Z^Z+}2c;M2GRNhM7MQ_#YMQPthezc#~<#emXPT z)j*mjhuR?Ka_f_0&Nucy`?m`C-bpGr9Z1#u&n02Cp{Pi&mgvyeb4X_YH}(0cPgIN6 z^`+}C5BF-U^q*M+m$=Nso2%G0iTcqkj)UO-P@EF{&Ndthhv7k?;GgjZ6Zp5hVU*P3 zMo2aUm4qZpOvRIi-Pi<=!>$R=sfynDiZ=Lnz_DM^Rv$aFs$@XdzwERcRjiP-?DR|l zQWaF>IiY00i2rJV?EFJkp1AW(j0CaD8?9-N>oO!}O|84K41D-29f-5DxeZ9p46J-j z9sHj91YDfb_ICwA;ssLv&n9MyKahB&Cel0AyiTy~;oA*{#|{P+DRjT#Df||4XYCOn zH@N)qlNHNu8@^}D7n>6a?T-LfdN(!9iyGn`O)9)xFC8ad+?A z!rTcxwih}szngR53D8YI3rgh-YJo=bHU(>5loCyt@!_fTO~;*qd$&2g@+;Sv89HmV zX;@Z`!$ZYS?w|VqF7@>Vg>fo4TxQg-nl-EP->K{$a~E!LD&*)?!#r)l( zuNtw6F6riXlsIkHJuTpQvskFFu9WWiu0w*M}AHm5gV8)AeJXy)i zQc|{iJz^E@SNz#6{m-)BIC0Xi=>H6`M{okxah_KxMu^G6+ zRl`7%#k2WpO29>x$Hw+wo843rI)3a)=(uMWC}CvIBPlP=qbThr(BJK*lIZbcL!!qG zgC#W)21g1dlI*ml6HP4sUKLxV8xpl*Yhc!*tFEiML$Pu<1|}^!>e$sR%BIB=BC*0S zAZY=JE7)*OIY+JCf@#Z}s4`t^vANxTN0%pE-k5m>QPm?aK?~nV2qX+j8OB%x+{JNI_tf)F<|yW9^cno~tH@s;%-3nYGe1C~L`8(zq+` zR|soSpnuim-~*WxzObmBB;9U=yIm4;H!$aL&*9uIxQ)LcVR1QsFZXJe{Jq;HH0H)N zY-2Ou#*o`s+-Ua5?BF{dk-Uc=7nUaUe&6<(Re1FUu>(iH9pK%dSD<#s?S}2arrtYt zCe{xK$#y@OU+)mjEX-hbVCR9^h0+P%*|^LLew^9Ialc|8=l3tH8w?FlUDR{>LEpz3 z;rB1N6XF}B6Jj3t?&#f6-OMVzqb4n$rM&yW+PLOu0vLo9{?y+&%o5$K#;%qgXW46aR@7s-wB{@Sh70*lvhh2RS=ZX*f#xQ|{|wv`6VWVB&i}A6$GZS6C|)PDeQwI#sEcNT@!BTovm1Q3sm@^B z#(IwBcE@e@9R`8B+mF?5aIW0OIqeG{D^3|UIkYIApChpCbB?g?3+V@mztKD^rMkiC z_Sp>qziOFlc=T(~Leb^rLGFAr3yoU4#(iw!`_Rl?RPvy8`>x}x{L3$xA2|5^5Gek0 zq!l3Xf3d2->Gs{^rq;_qYZHDya617E<9sJ*niOBoay!4Yk4xNtL4ITN`z9GsD%jR% z07(TIcP-RzSMTxzN#_+9*nUWE0jB6}4-Y|gO9OTDzI$n*VJp|X4kQmsv$qdHqu%Ys zL9@fvzx=rP-50!`!~gN`heijW%c=ydj)4;9#a{-#8}9v^+|+#Wh1GNBKlL>_;%#@s zlhz%L?bqz_o?*TGZM02yHdEwoK~E z;iwte!Ms_CYkf>twrvf#wo2-%bJU9LP~NPCYeP&|5iN8joZV*PW7o{$6jAn7{2_8H zYy+wme06-)e3dlr!rXVlELs#8u!=umR@6;-hl7<`q}q+_Zf Date: Wed, 15 Jul 2026 06:43:35 +0000 Subject: [PATCH 342/369] 6 dashes --- .../svg-images/svg-images.component.html | 4 ++-- .../previews/mempool-space-preview.jpg | Bin 70217 -> 69623 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/svg-images/svg-images.component.html b/frontend/src/app/components/svg-images/svg-images.component.html index 2e421b959..ec3942d42 100644 --- a/frontend/src/app/components/svg-images/svg-images.component.html +++ b/frontend/src/app/components/svg-images/svg-images.component.html @@ -318,7 +318,7 @@
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Spiral From a5ddf84aece6aab29db1813a77e6589994431d43 Mon Sep 17 00:00:00 2001 From: natsoni Date: Mon, 6 Jul 2026 11:49:50 +0200 Subject: [PATCH 327/369] Update footer logo to 3 - 2 blocks --- .../global-footer/global-footer.component.html | 5 ++++- .../global-footer/global-footer.component.scss | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index 5d7527b5e..9a04483e1 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -7,7 +7,10 @@ @if (enterpriseInfo?.footer_img) { } @else { - + + + ® + }
- + @@ -331,7 +331,7 @@ - + diff --git a/frontend/src/resources/previews/mempool-space-preview.jpg b/frontend/src/resources/previews/mempool-space-preview.jpg index d695b002cb4d93050751aa6687e653418072cfc6..3b82e33c00e0263c2c604b2b6a375006273a515f 100644 GIT binary patch delta 28569 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 IY4!F00B^G~wg3PC delta 29675 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 From 561e03b2858ff96f80c2bc0d686c625edd7677b2 Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 15 Jul 2026 06:55:11 +0000 Subject: [PATCH 343/369] fix 3-2 blocks svg id clashes --- .../svg-images/svg-images.component.html | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/components/svg-images/svg-images.component.html b/frontend/src/app/components/svg-images/svg-images.component.html index ec3942d42..4a7e85508 100644 --- a/frontend/src/app/components/svg-images/svg-images.component.html +++ b/frontend/src/app/components/svg-images/svg-images.component.html @@ -318,24 +318,24 @@
- - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + From 8b30ca7962dea32eef75a9fe07ee6607d2671d0f Mon Sep 17 00:00:00 2001 From: mononaut Date: Wed, 15 Jul 2026 07:57:23 +0000 Subject: [PATCH 344/369] fix 3-2 block logo colors --- .../svg-images/svg-images.component.html | 20 +++++++++--------- .../previews/mempool-space-preview.jpg | Bin 69623 -> 71244 bytes 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/components/svg-images/svg-images.component.html b/frontend/src/app/components/svg-images/svg-images.component.html index 4a7e85508..90b625202 100644 --- a/frontend/src/app/components/svg-images/svg-images.component.html +++ b/frontend/src/app/components/svg-images/svg-images.component.html @@ -319,16 +319,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/frontend/src/resources/previews/mempool-space-preview.jpg b/frontend/src/resources/previews/mempool-space-preview.jpg index 3b82e33c00e0263c2c604b2b6a375006273a515f..17887b1a6e00f0cb1edad60f5a1f711e9e25a297 100644 GIT binary patch literal 71244 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�-nFjzIoV z2kOTi2mVX^wFz5lw4DnA!CkItw&_2?$VL4wxS*SU);L7*ggPW ziqS5TeJ*p3irVMZyg&(PRgeTdZFV$=D>o?D9CRA<);)NCi4E8kR780nRR16$8XSJ+ zX=VB24LN{i|7r7F$1iycV#TC|XJ+Wv?oKZ)?tpJ$_Sw!X0#xfFkAVvZP%W$GqYxMt zTb*fk(zoM-$1@#2lsL`fL?26oTrMcw&leNa;+`?j|NgEi0HfyJQpC6J==g^r2%J8h z*qr&b=M9%45R5Qeos!KeO8QNQqziyR-L50|Lc{?-!~x4YodU98_**`f0i+a|vyY)y^qf&%f@fmC{|&CXRg zNmdq->7I{&LWl zoXx z9}E0rf&YCA@PR*Q!VWI>!5=q)fq?)ZpkPkN1b>hP{7n;BICLCbGFCQOOl&5Wn;2Nk z?EL0<oN)TDXH$yoHkEd>A z_vL8nYWA#(4TlR}g(vd#TwSfQNDjQqV$(65t~LuN~(MG0FS(O9^WG^woNb(|r<%4SU{}g{tC< z3+pk3MUUsP28G~7s2U|DHS>}HLQsVwLNv=bzdrLLWjYih$%&er*G<$+7GNK{44}?wt)axwu0r|cthTV3B8v;U^d3x8G)F_d)>andT zCH(wbub^G83^#md9zfxV4|x;W*eL9S^UWKt=F(@475WRZ7qM@CO{GJ3%aMN~!T)-* zB-~KvD`FmX%lZU?lv@N{S{M#)Sa(Eg>+?0+2(~W6j|5TZUeSH>-99Y{nUGccuV!ovzg)g(6CB8nKCa8#_65f5!OCsGe^$T5U zga%pm3ktyqjH8SpbhJ9A_POU@CSJ-12O{c_v*4!1;Iq`cA#Pf|QCRp9!8ED3S-h*I zXI-_HZi&*BIWmCLEhy+~oaKESL6!+h><=PcEt@{!6?9*yqGFnZp!G!09cFpNR*51J z+kZ3O&8s5f>WvR!^!G*JQn~25=c_%jjGuQ1r{C!2C#jzj`k3z#C&8$23ui#tB)70v z^LhwvZ&U0Hk}#=Zd_<~9$CzB7<32VzB^se1 z^5jn%Dufye_O9~Rtaw>6Uwc4F%ET#pL`TNrj}psE3`=%zQNMa}PXbbxrfH*j)jz9s zW}lZ|EznLa-LergIz#84R08YXYc;;Rj6(7+-vkxgsT;E)hpY$+}kzY3@yP0wO`J)>uiEqRBKT-E#t<>uHIAJJwONz56v- zA|e#pKD=ELRdSi0g|DOT-p6v3k9KMkb~cu!zzn4gQs$y3HX;#cs<5z9V#7`r^|46D zX_SIj@TNjda17~`ZiVgYt`U@s$9+-Ge8*olSF$&l3KgMWOqjFmv8pht`klo7u_o6q%UxrT?VHVWW7O-m}D;r%k{DSUBEW!JDvtAk{+FRg$QGH*Am+#=Jt*32qSK0o7@JjiO{VD zq3ZQPnm=jBZ+fXAE7s}`q>6Ii$CYIf_g0a;iNYU5ZsWw`*CI_j0@)RgLtwJcqa+Vp)`z1jS= z*M#Tg*v$ZF9WUZN^Nr{ljAjK$fg=77s{9`ipjU$kuxAg?AM6g zx}6f$!Cwo5&kQ!LLk)yKMD>Grci!|>tg&qVssIXHx#_1O={iBGPM2_%)tnCVoscF9 zktdS`2&$d|h!?zUV3$)yo;g0gO$B*gMH+67TJnZoEg@9L?a0J7Nr*D;FPg`nzv z$OrEz2sz*=m_Rq3h--UTN*K*i$o&5pe4W}?V6H->bVgr z_hLKlaFo{4P()HmfQ39+93S%Cv%1!q6zmcCkGp}`u}JpR<0MSU@V>4ix-X10p{U|H z1ZL@SACAh#pb5)H@Fx)G(}=u_5U~(N4ve(OE(&B2G)?v`etT3kyPoGV7txf_{#2U8 z3nr9OH1Z6Cm@kf3LDFF$Z9+d0k3!P%R8s z=$@b1a=7*;iJ5yp;gn$|c1NNsCsMy$p0a4A9)XcL45l-v1Vj%#sKoRO?omK5Ob3!i z4j{L+)PY9EB+i;}U3;(2z^pqL#3A~cVb`kHUZ4&ayS(`t!EHK1VA11IO z5D=w?A?oSm*A;{X;trlmufs`)quhz9cNd<7PpYeUk%QFCwIZev1cZM~6lD?Q5ZsMn zP{aOALO~7%+K~~Whk+X%t`&B;+LX;{JdmTY{@ugG*;PexKgiHm#JA#E5163KgcXG| z@38Z+c|$Xcu2DoeaNk3_mPGiGTuHB+tT6M?;6+nQC^eIbu$ZMpJrZYe6EJ;E$K}Oq zdK-lqC?*VouG~OUE;)ok+ma2!l^qZV52>5QQC zb@N`ayOvX9sBfm6bFZ~~`PMnRm>fcBj1!&!z`$<#Sq0DJP%Up2<&^X7to3Tv5xXBb z%fDALT9E9{S`-o23BW)`)xe7WweiN$w4YOhq@BzP_Py=v54)XhQW6W~N5c*pu#fyA zHxox`UM(uTO*>xoD$`eIue_NMJ$?ez3EI3MTZHtOaL1>1xpsAM_sT{-&uH$ghmlwq zaI1mHs*eY;{Z{$5xZHvVv{=x}VOhHNh4K-JJl!}opfyT!`6=wmHE$_KqhXL#>ue5+ z!PI$?nIUptEfNZLJ%E-iqq&P`tK}>iGd9~PQorOfj$gNk%1dnu>#{?B)bIrS&be3( zDe-N{Pz@WXGCXm-^b;|=_uo>MLG9e*+1HRVL5=XkvmSs>(IMu9w zfY{clCDlos-PNA7t~N)-JMMmmOzeQRH%PLgNKD^?;1aCEb;#(&z+s9=a{c>&?vRlns`xIuSEE?(<`Y(22YJr zhlf=HrD@Kae{kbeEF3WTY~e=e!^GM%g;Nwi`=#(iLj?zZhV)hEG^UDk!QipR@_ZLE zx+1ojapvaPa(Ii?Dox*Z-#X}3oebXQ34hOJ=d2PaetQe2ly~?TEw#EPS78fXOlZyu z+(K6qnx7SPxP>()KP{MWiy%x+3&U>yAVQyQO>!tt4t>aeP5OT+4DafD^#4&|0NrnX z_dgfSnzgD2m~GW7aopF6YE6Dqe1$UMX8#KHnE#IpVfL>=k1rSMj1W&9uhSmj(9hjs zsDB|11(RJJ+lufP77;zQ@DT#rniBK~UPX{5OxOOxf`AUsbKmc)X4KYgs0GR)S5~$B z>^*j>IM>hEEm?E7sB1fX*DT|GNyYN%cR#r=R(o#bBSrkQ!(ncHaOxoL;Uf^O(B2?S z^O49CCdvJH*R9aS>X_WY2M%$d7nc0=`i>Q~#kv-eoY<2Nn8*(oA*W6Qi|R(`<_u&B^ zDq4z(dV(zDSMap&bekfn|1p$(g|qBvspd;5o=h54xH(c_`7;Ud`}@4 zDo{B5o;CO75xaz`Vg+Nu2K+bGe5p_MnEyjPK);5i4dPZs6$L zu6jL4s!Y;~5WD1Q@Uehy7<^_Y{8rBLGfSAmIhgex}0l z&!*G9d~5C1Vkg5FBC$}j<(LnmG?mu2;D%gDXEgB`w{V(j80W3w`o)SM^~<%C*G(lk zph{??6!+A889|M0_CbwVZ3(8bIwxO zEkCqxDU6CT?jchS15I*WH!3gO;W#)X;xX?lKF`&L`uT2NFzikMCCCH0cgMMzr&F>9 z+RTlt-aMrDEM3Lxeb~`@eU=NqP30bF87Tz2M<9lN@WP%$IircixQ$KJ{SM4BdJd++AS zu~!Oz+QKsVx2kBR2;mmCNv~X<-1lnty`*s4`at#Pfp!u%=V%7fQq|o8?GSIy(hMa1 ztokI-w)y4^&A^LP^*^o#Bxz%bF+(yIU75Z<5S^v2on!0w2CRl>smtfsO1zo+ovL7t zE#jN6*9Stf)PJ|y0_V=lcjt8-v@m;`=|S1tJQ=h~-}V0258g<;$-JKtMB-Gnkx?lB58T$}`&qI~>bWd{K$VOxWPhKNT62cP3r9!^`A=0{$CY{IxF611@ z)MRkU$CJ=FE?_14;$RlIW;jMP+4Y{DXgolL9#*?k*~!TK1ZY*E$MqLU=`t(^D{jn& z#RUFBwl>h8da3$RU=!#)KJ`AVr{ng1QdLZJLuP;cT7UL)s($1^ZY6{_S)I>r5Ij1i z+>SV^dY;I)Cv_@JbhlWAqXCpCa0HXmX|RRs#mSui$tE0kjZ7+p8a)LTf1F&4^3b(D zgt{~7i;?;V1a>nhTLdf`Qjrh~BJc8o5^(}|M+6v5{-$^`J*`0kUH|!{x6ec5uA)HC z!9-3hAK&r)KGZpG57JH*dL+J#V!`!-eETL;7VDL(gebR_#>w`Vct)}rZL-lKyy&6b zWtRt%Gy_UKq;>J>r@RNs9*XNfAGxFZHj?<};}gIK@l_@`z$ zw+~7pI1DOjuL{{a&dwq+c$(*1(Caw!xI=UVj}vBfT-=8ZN{D)erv)OL+ooVDJba&{ zluBaox)I%kPn1Ho@XRyMk&WR$eOXK@D0dML|-oX9tyvvLDv^(v>k z_@Z8AJ2}=Yk@|#ZD4lDG5wvKMkJ+oB>Ad>FtI<|m8g|!D?c|F3{8%N$Y!KL);n}dxIs3(AxvACNB zsX0f8Kx3dik-a&VkKzI98Upklt-#jv?2*CGA6#U}8XdqMN9^nqNmxzHqm;`?4q>&( zZDK~pca{uwSXR2`qQ6HZ7@nsV0O7pns6)x6((Q(N6n5t--YcI*2sqA;^+g#Swz(BH zYb4LH3RQ-quMBuj>v%|2wx^#xYga}EC!Vd4o0so|l&T#x{d)tq+t4S#h&CZkhI~QF z+Cztshsg#N_G5c)&7P5LodMY&6sT}4E1*t*q(}wI8x|2r+o- zTXX6aSZ|orTR!J4EleR$_Q>#~qY?%$Q$ObgxejKFU(k_#w_z;FJvCVywYumwq+inR9`?UiP+C!xb+&)~1r!kN4Ns4%!* zyy#rdUW1XQlHjceW{&TJ`Yi4H1vG=6lU zGhAIBjrSCCNmGRVE_WI=y{F%zH=ZtMIeEhnDnqG-Rh@q>v6}hhf;XNjXW2wUAKEfQ zhM9$xkiCB<(Xg8A=;XZD4xR!>X_;1KLl4R_L$sNNWu3kMOOS~^Iynb2g+V5HWkVOz zGJ}tXg{9zue*)gH>gxN+8PA<7S%||oUQ60p1+qlE?7?jF)vqtoRxY4B*Za>CP+7vS zb*~aUVt8oy-FAOo#w8q40#p( z<<4^Zgv6kN1VW?=5@^00=+p4{k3v-0@Hib{c}zTif}_-s!F(5ae*lS&&On%ssp4yA zYDM!lE@r#u=i$vmtH9Mx#k%pE3#xSa1@Y+lwHslAK^r#)l_YQBnLX`U>*t7ov9&=_ z{7G-0X1DAL*oB{rU9TtbKppbN&$v?p+!fG2lkD5<<^=gOrE1e6%nNHjSPIr5wcka& z-I}MRMK`Nf*&)-Yr0_f!S4pYeM@h@}W3rfS#3CJ4Jkr~DbGwI-%33)@$p`#Qx4NWPKW8==2uM3YX8lBQkSpp0`7^-{GXi3G zE@cxx2?*QHhm)(sKhbf4crxj2!}#sFpLvt3LBPbn`dPQzPXliBI+Cj1IM18_L-tQB zyn_Riz_LoZnH2TlKN@<0WHPDfgF3WWI&@JIb3Ar1^!LC|vqAbERD6vBIsp~%!4e1t z{kX8KY2Tx+TCDhdY8+bPa(Rvm@5@T^76+RUbop$5Z}Z{%%qXM`az|@rguK!1ro2^e zkKZ07R``APVDfl=bNK=V=Pc7CH0@R9~E#8 zzA%+$rzXM*^!-r3plwxfYcSqax`g^6R*r9f{f0UeDwZ^6fI5o3?{;Ow_&{j|;SQd= z`sAv~usZUAe?y{$P0=zVu4BVwM`?xf4xT^nXmS@81umtKX8!pX79hW^_Q!Tyw2=jTm|@B4@!iqSt2xLuq@T4J4^gy8 zANd6`V0hhYr^FZKF8xBvL98>45ug>!z|`1EiC;&oqpyQ(5ujZ9h48+IW65{`uF}4RYx{1@tr?as$ z;lpcWaWR=fYWZs+T_y5;rO0S-?ev&@v1mPDE(F6zXkliCC_v%ELg6E8G6`d2T(6~K za!K-g>i8}bzDYXRy-m&?<4=}F3hT&JDj3N|)K9hJ|kPCT=Nh~@KMq8;QwJh}=z{7TF8oPTCKdGrdU5l5X%LP1=;^ z^`a9#fpr)}UKAY2!jJKNhM6&H&)%gV)6lIkNt4{g9h(x<)hDo)yqGjmmhl|}5wj7Y z&6LjgbMibMC{Ftp3G{2Pa%xhG^lx?7P;%f!&Nd1-LKLYe9#b-ANZp)MNMaz~Y!BCG zwXiiB=Z{S&g%U-E#XO43R`=a7*s#g~iqCvJmXAdW+*r1~lZpO+Y2U5Ene=Rkg zN;CQrk?3~Et72n}8ee%`My;v%@MpwWwU zjbXR#ZMT-iON3R7Xf0gL!BtsORm@GLkNY$PglG^xx39r=9KK)er&(;7^F||+dkHtI zbL{_xevWB(C5)ZTjgqxm-~(}#_6LnVfr|U?SPf~lI;Qu(h=&-q`7iM0^}1X0jT@VK z+#-&oy_UnR230TT{%%rU3*+VWwX`O7&6>Rg(!s|ykC#wlkH2!AD|3ut63iDA5bLd(Xw=*#~!YYvGdJ&mPKO2Mg-&Qs3b4Y7H?I7^g&^*7#~mcKFgq z(|M9{n>n;%m2_NJh8u1dz0gLp;(pR9eqiA~dHUEB;%&=0!h1LPpo7a6cb>fr%Bpo~ zXK&g{cVYD@d$XF&VWGLHv8d>^7&ww##3z6>B$mFHn~}XDtk6`M=iD;eZ=TP5E&u6iy^mZqyV=mLF1r~w5~8z1C{dlleV zDW3`YTfmhdX%DW|koDWZ3l4|RhpEgl6K|^%tF!3WR~g!9dzWy=)jh*qp4lSvLQzS8 zM%RL+G&liNLX=cVjOttnSRHiSJL`Jf4W6bVm_dtbbz^tE?aS+QuPYl-(Pms0N=PgA z7l;WE!uP^BPN$qM-B7L{=?{3y4Y8D|PDCW^t<+Ag%}y5WpLz6!-kQ-CF1i^$|Mm$` z;La*H!F*~~*^F1a>bPw=5EyWJo%y9ed6BkGtLVz!#zmfRB9O^ zIA|XKK-ZQfBz^=h^0~;&_n5VgGF<#|)ok8tSPQZH2l-IGx1uU|M9m{9tERG5w2vN4-GY=$MpbE=a8@q<`5aUiA& zI7}biTQ~vAjufDe_U=RQIo;=%8HkaGQ|2)yYr;q#2^90ytynDjJR*k`}5yNh)PtuNr*G|qB}P5dd-Wn z>nz-aPeCats(Nm(SvFc;rio9q8Qs0(X{Vp-7m8T3*>l|`o>qZ+s}o09&%Hf2HY6Be z%+--FR!AUj=#o!Xvdm1Pmb#A+=>m?-Yh~H{`T_EX@ZK~VRy+|&e#*ME`w%x>PnnFI zWyZQUx*vr1HYuiMrMwg$?*=7vDTpdv4`XC6d#WA$=n6r))Q<1md`ERcLiQoTkIK|3 zMIEI4TBU}&Qh67vrO(aQE?d&VpVAdy{1Uj1@ncjaA;}_RKb4dW9WfWV+^d z#US;wIOydK&G=E%-)FhDWyal3PI=9RCu-#To32G|Zq}gG4t9hgP%8HzwLu*8A}BS% z6zeZwA=w(PH}&>71u8)Af&X`o5!qtkPOMrEmgJihBkSV34E?_8pSsw}Rjx&u2zM_T z{^cwu?*h%5yBOkz(x2kB z7_fJzj8)pt4=zKI&ut?lK6c-Ww($CL89Ws|!(N8s1Rof?I%VNcGbA2_PuFjY>@3I$ zS&KTPsS&&q5rOKxkyvBfB;DpHggu1wObr@0GRmR%#%f}m8dt%H1XnNB4T-1P_7n$O z8Oi08yz&eIJt--!^Ee&j6Q8WrG-N>#Y6Ai=>FHxP5f7=S2c%7($p={>NOZp#OlW$4 zWK8q8sUzG!m2Ea7$P+o?%{q@|1B)1b_xO_TU!{hMNTwBn*TzfLD%Dp(O zA-u615tJc<^-xRwcpPjr*<5FsRJm~kZeBs0A_Zoi2t}(-fvRP-4cWX}r$01eSEfrM zIpRlDRo+pX&~B+07BNTPWe;+pH|l?lndrkSAg z&{lrYpflug=em(ZjQo-%H$vJ@hiL|f!H`(~#bDI$kgd1Y10w`}=TD5{vv@YjO4%<3 z#|(Knj}XRC1n4K~F{@v*TPCD~2Z!7#wr2}T9tXdsj$t3aOo-R~b$qYZ+#^f$M902g z33+b z`if3v@QUx}J)N*NOkT0yel2#(Bi%^aR7O|B=?ylAu0gX%SFELiERW#3L)?HCc#4{@ zz7L1|Z8!@N@9!Ov^WEt%FY;~?wi4BksdySvr#(&QJB4`8 zG>cbp)H};-bbYu47l~KdOGzLS0rh*9KTS|%6R$HgN~{dYL3gQRZ8Xziik)5=BA@gu zT>as%mYd>rTo<$-m zVXWtYYz{QXnI#>?YbndZH!>u-Ti|_R&%P0idhT+A#5$F)W{w zmM8w(>c0s6`EaKk#xo#D-+-4-=|K0@G(?KAV}(PvT5{@1FZr$fY$ zoDT69RlUmSyF!y+o&cP)>mTFj?nk#MSy&O6pfaS=Yc=%hWpkDVyz$19i_D*#XXz(a z6IH46&-g#q#LiZ?D>NOvHQ_7~qBDK^cY$@PKca)C-sA}|hic%0F>$b-3F&kQCY7q| zNz^ON83+gKNr@)_+6=gfClCIy5i|gcj06X6-65R)wbAJ&9tMk`Y$yymCKeevGpm5b z6ITi*emO16_2z)jOP|}Bp2siuv!Z?R`0fUZg@q76(X&N_51E4h z*{s6(ZrBIRfq)JHgg)OsPfA)9ZoF7G&X#9s&jUtJfK{dp!6Ccjtnim;mNxTblJ_1} z@7GqO5W`DhbTna-cj~dWR=j4C5{P-YLfng9W;y7#AiFJHlR)~)i>dT>Mf1vATi2TY zgGB1+zyY#O$5d9lyVubWhgA6oJy~8}q0K7JxQg>3&OFp1a5zRlaDt)cg-930weVDK zdsi-7DUA0v(hN;rG^*$)0Qpe~+LJ)kNVa~=7W$ll`=K;6t!kW70=)B;aFAFq)&+K6 zHNCY#seLA!A$rB$3=Cf^opLc33GR+yY8y%Lkvlx{Z&4xkFc+(=;cG={Od9y$e{bzG%*r6d4U_Q4NLPMz0_eEW-Ua_znuH1dgsku}e{bX` z&n+uk0(5y|fn=k4qX+&E?&%fi9IqX*%i#N)6x*Po{imy3L zsq%eKr_77BG$OQg#+rDU6F|zfr2nlJQge>>cHQGTUR`knE#0pued6ywGL^EfVHTE5 zhtu4Kv&>a{B(d+%_tK} z5;-cZU>RZuhrnn!G|);ipSgCGY*5s>o~9=-R$N2m=1TGmpB@Nxhs}_~yK>P-3^#VPYN@HI zeMOHJ61Sp}qw`h$%0T*L5B^wEsh8q5$r`nx=QixpK)ri?+5%ExC+xDk^0s9CgQ{-X z?JsWBYy%rG+qmVg`)drKrK_pOI-EHmr%gP%$wk52*Z-yoF3gm z((4`O92$O2oz!qCx+y*_k)VVgh@*}c+}h8(YQ2niG}80vmIom)uoK3r{_$Z!r#Exy zqv4qOdU9~2<`bZqJY=r@o&B&N8sH*8F2nl5%CtD$nEW`y8~k2AopUDGY_$cA-_(N; zyUQNAwBnK1-^7RB6 z$P|Z@SIYb*gwMi_Ye(*Ji;3C3XTWE*!h;3$#~F#3wPj`HO?;ij1Ou)*vhR%C*~NB4 zJaUbc(H2mzrV|Q;PV(xMth6D>MNNk4_)i!w9QG#&uF>CMxotkcOMU1Wz!+NdCjk41 z_?vIM@5{*{zo9dxx%+wVz3fYkN2X;jUMs8Ep)Z>Aq7_JnwQ|OJZM2?rr)5_{uh!f(PXio+k4Uw#e=-BEg?TzAI3iQ6VATb1q zV*`EyOmR7hvFgI=*m$?Xj$q}LA z8BDEWea^Kj4<)FW1_}lIX$uDnP5_(ZIO+hAH=nqPD7T-Jsp;%X4|9P2L%OmZ@wn01 z_XMyI#3>x}AfJBlsBCD2O1u-Z?YX?Bd6l1b?cHg7FOH=C=gLSWbPhP_hj$EpYEvzf z(c_WXY`~}{@e+LM->HnL0{{DlIWo!MK$n!b|1_MSX2cl(W3#kMliJIM>W>;)6!UoS zj}r7}NK!Dau_xV~{m?5bW@46YByP1k)#$<2OeBpgsJ%>kA$dnevq9I#u z+gFz7Ye0h1Vl9a6HZ9qVC&T?VVvSzz^X;B2!;T3B{=7W;+G+XJ{YS8Y2wdvqaUw$O zeqN7okUw1~C{|!K%+T+WZA(c$Cqu7d$%e#wg!iHiy#$E@&p1n|TKZo-hy=e$l-0x? zIrhl-J%xn-ycE%nO=)e3^@||}N|)M{nFr*tl)cGIvGPbSmZmMf0xk2 zmXX4%vmXX?G|Irt-e=SnJ`qk|KSEr~Y=6IdcvqfNiq8LqTfa0$n!iVc9-@kzx#*KS z0y{g=b{M|bvW-=|A&{QFB1iIROLv%%kb%TV*6d92X%X<>YU`P6HtwV-f1edoq2u8# ztGb4eTbTc{@YQuG%zFVH{8Xii=0VOLgoqpw>!?JMV$!m52f`#GSLx$Y?ze|~RO~-a zXcp&6`9hR6%|B66(vnC+8GcVyTK1)4c=6uR9k=5ZJp2dkJ#n;krvBHN@hzquUo(n^ zE)l3mzUX<3R#nkBS4duw-t*`#BVlX~mrpbBHT{h0hNXN!-X zGo}TGmqrg|&%7|+j!kgD-J;^@X7jd3B*h7#OR*|w@thy1yVa2DQRko~cYDI=Bc|@S zudFb`&%H%ZPeIq+#FwJ_Izq41L*n}Xwf7cqRV`oq=pm(~1r(76l}5T7DFIPHK}tZR zLAo2HK>;PCL_j2^yBlfg6p-%D|30XAych4!y6^wq=iRfn`^?OqS@E4UYt|Wuy=}+X z;@X_U&8P!I$U0XL=oZo4_Tx}WFkZlCBhPlk-?)L$QX*^PJJ5}r1Wx#LJ~R$7zo<*M ze~bXe%H!OW8~TZ`VHeOHkz4|^9*eahuD>RKt-gk)+tL08gDjp$12!y^piA>f8!nCu z3A+8k6yp0#uIM!wO8lE>Q?OFmCUy@3v243y@{stAZxXdTQu}1E+fj>Hg}g(&ES9tr zjGbq~Hs!AlcTj=aQXliG`GdGOQXsNA~r|x3oYW$o|=4gaNX5m3HLn4IKyRec#9X)WLs+JlxHX6C*@_< z8mh(Vr7C63vDX8v#0)ept-k*N|BMb%6=MQE;i9&UB-rBY!#d6Ss4$&2s|+}*A9wkaV2=BAm8Y4Y6Njdg_uidza)mu4sL4=KO- z=2pdUOmwh2uni-doE_QEAcW~H(+tsEwJc^U5P@{p6gh|Za#uyw663DDCV~JDAjKKFj&WJLO-~Hr^EMs|NJ>{JCF|40 zudT+H-oq0j#b{&I3S3>CXfwIQzxqP*oL&x|zjG;t*klE_{H_lExE7Ae3-%M$O)_N| z@EE!B$@Su#y;c#jbtaSZOwABqRwUe!fV#V|E<+*e>#IE7W_9NEpauv00wda6zZ0;O zEu~=0Zu-Ec*6`IN@_DVUR*#ysu|5pFmygur{7Gb#0F02g-OlZN;kN@TUT)W&z+Wdq z8e`Z*Jc@Hh3dZ7&L?|UR0|AVk6$mKyP|C`5|2obWNer`oMw=uSd zSit4R@#TU@(94IXUIYv&DXxu=oR}U_;VqNe-xNX~XH3-WSzN9n?LAR%Y8P{j-#XM~ zhcrI|Xg|_+lP|_ex-3#8`r6azeydNl35xcEdR~(@+Qp?@MLNS6g_Mv)xR!8?yUTZD zVlOOQdQDIgz;kCW3D?qcfEqVXhN`=<(`Bj!A=QdTM#j$~;5O=TT{QJU7jjY2LMZY; z(z)4F*n~RF!VL{=Ho+})A#Ipuj~2WYpi)7;g-v=dzFROa^J5dQIrQSTr1O-9%}ZKvt`z*#3AI9ud?2XyWJl@L0IT)X=$HT zVQUUuqMXV`tKa6`(4(^XOhO}G%x zALd9j!dFyO+NV}j=&X_^ABoGNBdk}U>*A)DZIfiN$+e^XS&E_W;LG}{e`y!L)4%^L z+G@-Cr|vEKPWKwcC=ZS3XWSd&OIM(}fgHg^k~5maFTmW-eT&oflAY4T%V3}N#-{9` z`-?J9m%9TCkpqol?)_tvgWD=(J_Na?;}iJPxU68?vy{1dn(h6iGg`oA_8l0$?TklY zy8${Q^?%sSSLISpQ||7VRCbu3RD|VKw(OuCc=QPZ6cyiA7n8br0CYoOPywLdK6Ox) zI{%XhV9+DLV+sIW_p~wfja>O1ih%8hQ6?Ubnl78mV5{J=;ylLACjyK}j%j|=^(WQy0MxVc7VxiD9dr#r?ThjgO|ot;v~u*+dQT{B%dWK+73(l<3)nU z*6fH83AuLZ>d^7$=r&ELKMw+8>Kp!*0EMBeYyC1w#z-tQKs zi+L~UI0-I^+W>MULSedevY)E_rGR6Wv_N*IDnJCF;006%Y6BFAk2Bv=gCP8uq;is& zX)Jl8Ti8|1ZyKoL9+nxk|K?fgy%!HWCt_W&0M4YF@oBc9Nbn5F z8v5-V1f`zt^8|gGyQ^OfzqI&dN~24sJDCXQnofM{?&?}#V*45W(W!_;R1*Ig5@?z0 zax$*s-2@A=HBW%W;T>RgpAH?oB^M463iyCvpujtHK^Q~~%jm@Fv6=LI{4%m8740iQ z&cZJ$($}Xs zmDl?SaG)?D?{&PUA(@uqyAsT%uTSn;K2H z(M{AB7*{HG%vDVaMXF@ZvlG$JBOQTqx9Mfj6P}rJ%Sw4zl_Sv7hD1vGd)7P2`r490 zWhZ0CGK|`kh0}8uZ$Ix5l{+_E`>!N-_({u=#XbQH;Xm;?zAW z5BSQDk7IPV$HWXfRJ(ml{7Oxs zjR~?BJ#xYk@HDraa4dSf4%&n>^=U7lvuOCJoLej}u~1i0Fj~KWMn}1V?JLhGB2t5R zUK@rF+XB68S>QS+&v^}MEz!iC+`N0Q5|zw2L}v(O73<&aEO37q$R@m8!JPTn(^#n0 z`wX9IM*I`cH{<60(%OSrDD)L3tu9cS7o62ib4;frNYEuhCA2P8qqCtFrZT^bHElL* zRLVCec>-{da$hts>qMcmV!Zu~*^3!V&5>H<%DI`jvzlA58c0tM+C7vow)oW?W$Q$= zap>YI*^r?b4XZCR>#nFZ21u_N7#2z0$elI|=-{ZF3PH^Xp02!JhUejy$*lJ2Ojo9k zbDtm(jp<{&_kN@Gtnq6KxNszxB2u_O92U}zY>LZX2Xbf}&nlm=QZVh)EZ#pkK zb7+v-^Dz1S5Ngn_sG@R5?0d|6&-M0^w0j@LTvj)%tH3eo#Oh6|+v9~NSw)R+6Kb%$ zD-phTrTbfQ%7#8k%_WW3-l?f1Q~7Mh zi?}~iS2Lkpo7H(DWiE79(7Dj(bl$M?Wkb z-(`dFoOULL`jcT>IuzZnk9J@? zg7Wztapsl79>x4LTo&ST%{J#z1&c<1X5-95I18AhwwHOW#_so@H6)%yeSsG^n<*a6vPAY0c9lkYW?Tavc4v_4+geXM2a3SQhT=b=c63JpS69LqAaeWsPmj+6Utay$ajpbuRlg87o@ zL{OMqPVlg7*APlN97wMSr~^L|SgGvIjJYifTy$sr1#Q*1N$_g->U|0jMVGnJ<aXz#h5n?0rG_7fdem?`ly}Y!+&knDR_brXsF(Df zDTF&4CMnVz>LN4`RgS-SB_pt$X^><76^_b2TZ}SW7!eT^EZgNbh_8Goe2Jd%;hEz5 zbr9^yrMfwKqC11em7U;WLaMRlMHTl&&VVF|-Pq6e4G&&KVJ=AxZ{QKMw6r_}zbai_ z*%J^HM$B<28Y?K~axp5Zg_(zs(YaA)f@+FpOD;7%A70sWTiym6;aT&2)ZOrCB0NAh z8HK0=bvPqENqT;S>#O+BN)C)*_a%u6eIq?)?EospMr(n-?q=2gGW3#0;ZbUZzQrC> zw2t_om5XjMY52RsD6A;LIVCYgk~^m8lH*$YdfXz5fGjXx6zI?wJ!Wir@feLQn0C(A zZg|R;>Y6)^A-WNJ5*HQiMWLvZa|hdbEE>|HlJpO^Wsf78Vr0>l@*;S^V3^=%@?s%q z%Si`@02+)gUA$+|P|oLq?jjmEr0TR}6Qy4G{Mq=v+v}8T7#Qa*P5yyZzso-%la2st*Eoh#jIRhr zUf?_oj~N538UsEGJjOXg=*Ho;W>z^2emtb~nz!^5)JTR!9ASNu>|77BQyZ#+*CyEC zMGPk@>Y5;w%ZQL74AfgdMRVY}(Bt`ez#w?%L1`K3XyYe)#rVQ0j1RrS&|?Swl3*~B z-H1mpa;9X~+#?a)7YNxR% zAHfVmSAUYBE<38;FEX4u*6^no_6^l?@3^ii!dJ$?kNG#M;}me1&2%Q-h&uJ_)@U9j zszk%7_dXXA&UD&#&7HkLjE96!t&VSvT7K=LAi4+)Mn<_hhWsdX>HF7UpyL%|e?*a^2Ra(Jk zKR#r8qmNrYZv#wRwJC6F*9h>u$=p zDp^(uoF2UMAo7JLRd=c+NiDyl)PLS%R^u`$)a~s&4F?Jg-5pd%0YFhSTEU1PS2Q9? zg$R^i0H=RJ+UktTz!kTLs0K}m5RhGK`%er3kAe)H5TEK1 zq$j0;^YQV1KCX1)pO&So-a$RRP%3!a1#!MpF4wh;xOII*waiBE>s+TEJx-*|7V;c& z;zCC>&NmkptH?T;y(Xf4k{S%t1H08ctCjdi0zd8=^(1%IHhz^S{|IF1^NqH|W$AEF zzW5s>vVCk~CSv#SLqRE*^R)d@PYD21eRaSV<1%^ftFuKXbH+t?ws*P%DGr8;Uu&D( z2_>O@Deb86?C;M>9OhCkq`v6om{e2~r_WlSW~R5PhYQu}-em&npJyf%4Z>b?(TDYr zPSb0Y6r%>sjrN4S=DwAC%6DeNW4gC`NlTwlrw9MTtjWRKU5^60rAT4FgB);)%PMoU z!WA9`k6x|D2ZvfQ$W*uvT)Hx5q+YIjJ(%vfq4Xs#>$Q)q(L78#?eQWZJR&vHL7Szb z{X2wu6ZYxOTUl3RVg=lhurE_Mw_!D2^W5QZm$JUq6Y}|@%DG^-l(Saa^KMTSYr(EH zXN9My`Zy{5HQAc8#?#ZXr^>ouHc2|cJl)6%ELX2*v&fYE&&r6oy_~oK2*kfTB z9_30l#U|G4ze@~K3PIs9ZvllDLwf=FzJ9L%yF8|?KuK%yfv5~Y1-UUN*geu=x$0qMW4TG_&-W401?~5 z;s$Q<@j1`YTvt@riZNIaqttH9`5z7crtK;9bHMrwsQ4J%ZBNC2j6~E!gF0u&*ulj# z)%_#Q|3{r3Fh@iinj9+AZ2jD>CW{D;c=#&!>;Cp7)t z^Baw4mz(Yy{7Kkv=23}c3rptdDZG_hK)O)*;*Y}qs?&g_-(af4fF(sL8&IcT>VH7` zBe?Z}X+$?KK#-z=NP|1&H%=X3ph6#latG{Q*S{^%GV;C~^c&RvDoK_YS+F>q_ke+* zmEmYP-Zk1+H#0K~;uYLyIf*k>%};H2kF%~t*x@!TGOIGO5mgWUEjaQ&B{ zIu^2=xPMvHzcn-jI<2?CEO&&1CoH}jL(7ZWFZ8$i@f*bvS)M$+qIa{u(Y#X2Cc-4a ztfS!1!JY?(oh%q1;P`O!&&m85qCrhCabB=GX@5@U&&2<@wEq$O4}t#>_z!{q%Lo|B z|C<8D{hP#deZHBw{;;F&r}@JKzkx#M-z0AE@4{a3Z^Hf!O8-|G^&=h!E1yD^2e||M z5Lq#nfrqf*XyfxqaUyoV;}NhQ?e9lJ?f~#i!qbjzi<8C!29QL_zRi+18I*Q>Wl5HL zf{qUFX>@^2M*{}Ha|%y84wOzB8{|Sm5DhZO=7Y#SS;*ai$OhjyHd}JwntU2raM_kY zcGdUpEqt~5zAGo-0g2ppBFHSuz^L07Xo2#pV1SNC%*(RVNq@}kh&cTYumSWdyqYf| zs8TBlg+gR0C5!?wA#@D_BLp5u{lTiIlinQ=|IQuY;kaM%4Hkf)`rH6h93mThyYxC_ z))Fi%*sbY`6Q0g=%is#II3GZaT zOga6|5eNs6_|w@(#_7Osu=q@{Jd__@2fm#wRArE{LVsOO3%8vQ8hIipSWN1zm=@)e z5xD4)0-)8WE)AZ5kcFco_&P-P>$n=w3~bOtn=dt>0CsyC={O+^1@ms&GnJLeoegyg zbDU2PzCI8)pc9qnrz^^U^>NtV0N_l`hWrDCLpJ_d2(17Z#(4ZMe68tRHc2x*YFaD`my zE79DAFVzfk#ZW>s4x6Srj1F@*y+GMwq(`6!of3N+^nE&rmWPcyWPXJ-xsRy~$alAZ zCra#okt>-j1H2;k<&*s~=t~m(ZSkdAZXX%r-Kssh2(hx=fqo+BcCvtjXeqj-t}3k< z@{v8NiC~uL+0IeJVeu!-DSMom( z|7YuW0(ax*4{ArS@|QID`zxzofd7;4`o{Vt#KpjCWnaz*Cn5--Rn= zko_C-h10KCe?NH5B$+G_0ucD@mthduKVkh@5Z2<+5iAdp4OTkA=NI4ytKJ{{jdus& zXNUB|C**$#esDkPicx+glG~qpg$yG5wWEIp{^QokWd0CTvH|!Zvbpb1GCyzq$8x@e zfY=~+xFc8rLi|3Zlgux`p8?1QIG+V@H+(+z#Kj1{q=7=ClN-MT|0nAwfiy>8YA%P! zpZ&`0=Y9WY=L6~c0qg(9_p_X+_Ve(6c0Qb6G01)25iAXn?K`3VD^u?$=OY1W?Ns7o zW)Qvt!f=1Z`GBKeT`{V!5DyptQ}5|ls2#MC?V@|(z~|Khii z&-K4YKF8MIAG`|DyP^GAkiI|YEBv~D2|Nv4z6m^2OY8p@c!I5eKvMh!Vjv-5&;Xfz z_%)MI^=;sp3;coLH-TsFz~2H-u=6p^!xokSe1qKb>+}u6xn?i6OTrxwQgizw3|O`XNHm{9w|Mk316rpa zw@rkgMpk5yn+1VQ)K}tPQ5yKK@c+dK{HtqDpUnN=GJsFk{%>J>>}vik;{WmrYwCZa z?>_|oL*PFI{zKqD1pYsVKpyzn8W_Nqbtou!I9TAF)!^rA01wx|Kqq2QHwkKACZ^|; z$&`()Sm8Hz9#boi2|RhZHSoOXiM6X`OX=x*bHHZ7#g7>wN1)@i_ZE(g8!yb)^9IW1 z@dxGxUfZVw_t|I$%MO+do-Xiquk|J6XFvV479T#lt7V8gdq1SO#MV~tb&8(lwosEx_OQ7W-ssvQi%w9k&0nddw6k|nsiG(evPG7Kj}MR^$!wpy@SRGDU; z_gdio`?6^D8yu8&W+nN$dsw&UF*k8uyUMoUv#V5P$BT?)HPu)rIXsf<)f!7!Xi}+c zj{vhbBEaVWQ-5W#PN%NBjVRYFxRx7G)oU;oyU^CPc^+VsZ1rY-kGWXhVm)2m6}a7B zT9u)jwK=W3cdnS*MVDW_eFc>$&l7$8;(cG5l^UG&@koaUgwN}yXqK+|G>@;b4YqVn znG{4CKEQgOZ`U#~WwH~g@&NaFfn7_}6uZ9N+S^1my=lXdYZf~ZdCh~R+LLRnM$bwk zrVI%g?9Vh0@@VWzKQFWch!{qHDWH2^WLL8eZsIcyZh+hI>HamdorpxBiT2nUD;iJ- z;AW_LO&x06w08Ju%5~tz%o^)4wW=w)xSfcwFZ{rDTN(1FSTELocsBG1KkdGGvmv`* z9ers4L(vp&=M*bf?b5TsHT)Oq_O#%81%7qM)hiiHI@7k_7iaVG?-#K}zN5VMxOx21 zZzk}KOgCqSe1DV5N%ITcDh?F}+sMdN;+2(+>q;CB52_j~flpfo@X4m8rY2qFMaH+W zxPXKEG$Sh``wb1ji>)~xji(7$#BY9kksGs{T&r;n@{7dN?Zn(ad6~A+}%T_ zbItZ!J%NoStU5{qMt9Xeo=**1{oO{VK1Pvb~wtz-F=dp`V9>gvgm`7 z-8!aINcoK~?KBM}E<5~HQLx@3%fEu##g1}j~%+QZ(9SseoP(*2QUOT`?|6FMmMSiDpx~s zs;fx1f-4y{A85#8gUcJGrcR{-Mt8gZ%uX~KkU~Zl_%gf$^7p9?W_ooLRc=-`R(51$ zTiA|-dQM+JI)#f!3Sd2_*zV1zrN7`lSnS>BfswUy1R6E?S)(|(Z*`gxhtU~^p4Ovu zzu*lTPXR!j@$N#UJSto4fh&s+X`*2kw%y1c_(c=t5$9{-x9 zu$-obNW((+X*Mri_e%~y;IzKr^#nZT_-S`2Kg{EdIAvsIzoDj17k-;KWyb}ijvfCe z%jfy9oSB!X9;MvOHWb7{HDI-Hp3veo{R^h^#_BX!@T*2v!tx|4PBYwL zs-HKWgZtj6s3JN)6qa|R{xrah&i#VzWcr<=OT2xnFJMo^r>oLk=GOVuF>@`SKFjF* zB^c_=VJbLHYR8h5(Cm4~xKm6}w)j#i8|Cfxugp3{Pn&liY`^gPHXgD_JGHm{wn~|J z&!UYTmcgB-^)~l?(fE_;GyaCvEJ&A5(0r;I~28+CxczRZ4N<*rbH)PK%#YtnmL5 zqwPc-Ie7eBGmj4KeB#GxTf37toN62jXQn9DDyNw z5wOK`DK|OuyN2W3q-9SzvTI1_I9=+xbigWIwm!sj1ZqDnRCSywus;I5n%p{!2pid@ zdv!P@HLz!n+wN%iYVx2kB5`xpFnS=l(9tk%S2}pskdVA>L~;>O<;(4=iNYOxqMlCYf{A-lBP6qi*0^$y>I)-Y z2zL#w(#Gv7`y}U2+!J1B$CWd$FZ7u)rF}IQWX=i{7k(}VYWvKr(!N>>0%QY-w35uy z?f4|`)^w?^;mWNegDXn)0F(g|CIIC$fHFYkBS3}ACmGDvPilWIu(gXear(Iau6x&k z06+sddG}n`0n)64yE(6TVRY&}>G!d_UA*F+GcpIdgsJ?F4T2j~$-5z4s>=#;>r_c) zqiKnA3UV7M$-B3B#evtndVj7dxRK=G4j0+s=^fqVs4#ayUv9QAYV+ScgAqaw6*>cg zM(>5qG?)1PxwAf;(PA`t*!e40?3ny=&^r z{6Vo}D||90neE21&J}?%Ruznh&(UXY%NYG?j-L{0H0z$cTv{ff= zT!~kDxe^|)g0COGBGk@fUh7H&)~RBy^)^Up7q=Yn*@qAIyz`$Uhk8*faTsJc2s})q z`GL0T%*Vde7Q2-Jwv6%yz6Y6sVkPg`98EVJiKF)#fE^Q{@NKF32Zz;Tv4Q@J!)Rz# z6}K8psE47J&Y^gZ9(p)trdcw*zrE>QhOGWj1fTa{II|V~;7WH`*_?mt+;tbX63fHq z-bbL1ws3b#SsaCSt`Yi~zhcECKkqYk&eNx?m%k_41?ys89gY2PQJx9S9YE%N$F2_e zJ#&n4yX-aZyGNjs)DNZ5?`s`1{AXx)*6oK$&FLOKi0qhVW)V@+D8oGj9wYcs zdQU~foHpPM9UW@BzwP3NSCbqO%G3ni5y%57K7c|^V$euJ&g+3i()fMZ0O2E0;&zao zqqYVqkNt2Nur>M2((!-YE!dLfhH5;VJeY0$=A1-CxdH6 z%;!DR*gV5!UNrUVd|hlCJEb+e5<8cw>z)#m+X_W$cs-iKidG+|nj=o^)s~xEvk&kp z37UVTKDlshSg7-63NiU(o!eLe8bQ{Yl+cgn88k{Pj4~$#-!F=0T~AnGdaR|=dOP11 zNdP_`Ifd#1w|g>~&ESaUqsH@ODbFv}N3sjZ+rM%~<9Kh=!c)ZEvM{@5xWr^BQ^SvPi%q6E zwZ5o5Jt0XW=FP1Tf>&t=&rLdSgC@Kl55VKy|}Iv>tNL2xPiXCr8T$6JiGm(>`@6dJf91bKb%6+1(>=X*qO*<9iO&Lssek$A`6_=FYull>hNT03O!+ zW6*Wt1$zK%!$GSDp7wFwA%lmN9F`kD)=_VFQ3Ar|ISp{$q)ly}@8nHah_JMdFZ8&I zpb=Z?p=BsM>}O9Scn(hEWzqt(<$RNW_{*e?UH9lVt^G3f5dh=)>gi~}1r)ar37&&x zkQozX1V*VyX=tpsOXkg67U4ykT6I!4Z`m9Ov`#Y~s%9)^-_0tD?tLJ1@FQr>;yCg@ z0n{=-R;!AG^2gMY6bbZT;c=y$+(T^=PN6C&@i3E~>z*1K)>#DP!b>uaeXbN_8Luh< z*s)^rRZgjUdyF4mC12%se1G1NuGiu&&_8{4duT#G-scCN27ru1J*^q*(m%+FkQ&k^ zA=6-BEm;;J_ZC8?NlkwU@*~h2Mjq9SB{1RNhcvK0WuveF{9hKXb|}gauA4$Oz{55=sf}{YT9Le zhe*(_s)W44qxtdg+z)0Y-u*FePW62~l>S){(I3`>2QBowa{dpn04DP5i+K=pOszYl zud`5VT>Rv@fCvyPBHi`7>iD(o-i-pR3bLyp(*gAksV}=2mQbf4=9`JWJIat#ni15a zPaeqOS$}o)^DBwCrVig5O=9ajVOSB*H@`cY2m=o^*)I6fK)5?)0YSXw1xSvj4xv+@ zX5KF94Xpg$@3OmofR0d^`e0A3)glH>?5u%y#bFftYhj%I`qNA@gE)`o4rBZ@cSn=r`;* zRIQhGPfRr*f!2;drBxlg%5EMEu@MkqNnh|f2gHOv>|Fji%t*R3M~S1k{}$$dd}i!Z znvkzKwjMsi`{xe^@L&BKE5^-3U#BD0n^ouHfTyZ|4Jd{mGNb5sf0;Y)@xY5VZ4#z# zrYFK10ny`#`V3(PlUw+c&>r}F3TxxlvB|0?m!;9YZQ8?2_5nO~(f3<3KlZN8TW@j| z0R^;mVfULdCwq_Ud4{1;TvFzGW}UI7K2_D?83CKfZ37c7ju81)TCTfC60G`z@JMy+AUTYv7VW083rO{g#ZPqD%L_a+tTa;3`79$0X=m zwz^+`EWzHfNS`G*zW>(-bC=8mIO?MAzsvlncyA+svF`c(=FBO@FN{2NB1|h)Ve=CX z9%~Ym*}e>HTX9~gFpe2>3jDm-00lZ;Y#8P7FkA$GrbkUn6|N##-CjNdS-)0i zrVFo6M|^TXkMXE=SepLbhomhysaLL=vlPWe+)b+eqXO)#2*wv-xwp&jWM-C6kAt#C zozb@t#+&c=Z{rh9CP9&^i>|~H-Y6N&FW<`_Z_2^A)V;bh6udjVQ5ltDEW6JCg14cz z_9?#OxI=@I`W?b~99T2qh|#(ChXpZsoiQsLZ4|aa^vzJ#s(k`*5ztkxsARHc#ZV{; z(*m~deLL&zUK!Lg1X?+2)Unc)cJL1wZ5p%kY?eB2`Qb)^#@q+t&o@TJiH} zV{n^nittdiIGCIPma%xsre?Ur@S)Qe%ebDS+phbfQQIJDOE^?9;5{5& z%sT))a9D}FVyL6 z1M6FwL|da#!~&n4Ex$urK4q|KElV@;2q!Je`V6WjObHx14ktBdqB}x=9>&y}Rd3(r z9puh(l}#Z5Dq=WnbJgVm&voj6d60@8Nil`=dR4$urDnqY;zd0R9JTYW=*k!Ii(uBD z55oHlL}fS)EP?L$_lDKeMu-!Q1dTpo+2KkGU1j%CxYN}H%W`?Xuhf;DfZEHoJb8(9 zN9}DjuzMx5jvhhV1*)aBAFjvb{QwnS3DH1eHOU9ZH-!#unXu)(as3hP_1;1%7fiKz zby?r)_fO-gY7?bi-Rv=pceHZ!CU$ow4dxlLe&fCToVOE|w+t~j#DA?IwO7dr9$H}p zrRZ74X!=vio;+An!Da>Z-7}I2Y66SKTr$_x5ZpaPotpmV3U=1v*K$F(*66qNcMXo7B?vJW_Z(B4K+!~HRk!J||F6vc} zq*0V;nxqTt?Rt)+Bq=BQ+_R%Od^+rh-{Ux5H`2#OyH}T?^zli*h4RbvHxFlCFLUWB zgirRmAP^m&Oy=)gWz;uD>BkqxdQD#NE}FeXduic{%8PGD{BVfUxT#gs7|*X?^>CQe zs`8z+j?ITjqZi&6N#6)+hhK`}rtc}KoKa=tg_=cAWFB)Vi=wey)R2PTS#RdGFD4x= zs)$Oqikj17FFH%0{ie;Av}}n#;f>hgWfsES&M<^Gm(n(-S;L|U81J>I9j0Q?3Cp$l zqxfLy?LS{38regP*y{53^s>ohkA~qSFq9b+EgMwIb`RU=Is#!Ge4rZPlx`$TPdoxW zp%9+egT}rPHe51z#n-t9v-VsMwL;}yv`jNB56s9c8^wnERfRjZKQf=|u|>aLERL2U zT4sckGD$60^YaG1nW}*veU{0K9o+uk#I7z}9?qU|DSBRsTL`Sadl!rV2QE{zUn6 z;SZy|^w3&jZe53Ul3<70K&Y8^B7Tf=<^e|ZzD0+VIMij>7)+-?)(;zM2qjDT$uHpW ziLIoK9cL119{5w*A)klgA$PvuhEKN$WdU{0jaCv~P17_I6|EdZ54=!{luQN`hET=W zB`>ZR%`~+76)~w(fKVT?!$OOxSqfvYZ&DDah+ylR!afAZVObQV@2?>&arb-$!6ow=P=t zx=}rE%8n76Vm#|+Hi@!j!|ed-=I z`^YH~PGziKGaHLHoraK=(5!7q!^#IErS_c?eQ-#k(kfLif(~PVhc&nY(&QqQn;_0U zuJ%9N((1|uwmYRqqN+Mbm%B_+Y%Wz^9d99QPrz4@kam#d4^2V`xr`1YY=g`%ix4Hg zEQT5tVKO8zkmwD1)_{u(bef-uo8~f`ty>JMfX~*ut_RC(YNMw4ojC*Nw_By14P%Tc zvv{f$pAlMT&B|JDvCdxGKHr0fylUlpxxY{rBOj=vD-EKjm*sp)!k=e6C$PR`)E!+u zA{%q)Cs2})yu2KcCk|tFnUvZKosp5<4ta9CkPELprytN>(We3dw-4%I8&@LJym_2^ zswtU&MdMMsV8b!Gii1!lE2B08Ic71Kd@lUBF6bFO{4dI&_diN;S@}oO403enkk{`eLi znsejA;^sYZ@tSHBJ`QaD0LrZQ6PtGjUob8{CJGA_egq#^+leqjK}zjZABj3+dA}Eut}0 z(Bz{G>4U!CIPb*(wBJu_Qax@@a?zR~w2U;XX96hnZ&D2ch;ou}SrY_WSQ7-Ha``tY z1oi2za?+@XY6?97lBS$AH$=6P>*u|ES2>9!fl3%JM72`u=W_}OLi0$ofWW^GTZzFZ zgUBX=6*b$}icb(dhn+h#kus}mVw11Z+CJ#r#HOnAd>Kri!cbzyxd=V7o0(dyCkpTbYyYR8i=S8MS|S&0h!z)4qYq3SzdcD~Qy zTGsvpt?hSBf1`cv)8rV$j;!|mW16*j1|9? znRV;@O%WYT{cG1FEJTJ<5)<+(gWb{JP&`dyjPdQv?K32JXy^`&;fjc zald>sxVPkO;kk1I#RB$N)6qh^l1WE_RP*9%k%#0!Z%UB^Q#DuFTNu*2*xk{dQm16naG+c2InD7KJyot53 zW5p#jHs4L(CiJx<=1w0=S!OR8Zc3JGOSr1ocNHa>lnIgL{7b1KQp4`fjzM)X0WWzh z@muF*L>><4=b>ij2{5Y_e?d0#+1{I`xM?9IZ*zeNGy zuQ`%3G+!9PjdU}?FF2+#_!{36^>CqqES80P+%&b=PcOTxGbQkcz-mY3HW|-Jm|aHq zmC>kxcSR6j5TdSpdPPiMrEdr+W+lLlGH0L3ftB@l4TJ?WK86gPfmx9;Xk}O zLwI6*%a%4lTHtB&ea$4sm%eRez1l%6^L|AN(h?@_zMY;=~ntJ3IP9!-*yk{9m4g9=ZqQeg-=w? z8V|2|l3xl197d!a3E(h%R{w;EhGkg_!em|LO&phV+&-2-;8N=xprefE8je! z5F42H7}Q+(-H7qxaMZ=IU*^*3FS-j`zQg*b7aR3o>XG}WAuKxj`$O1LAc^b4sO9^T zw5aIv!w0|Z2?3P&KN2D|>20g+`x{wi&E8tSA|A>DOt9Bmprt$>Vj0#05%78*-c6af zb8a2L#D)=WF+fw5O4Al2z`*YVdjW+Qp$7del1u`5rj3r7Ivl#@eZNUW8O>Y1B7l9~ z(&sUbOuW))`U4lg?e_<1PqZk1tafBPWktzv_i6%E;W^Qdh~ItL9*5aFPXFvb-JdQV z{OyUqQ3gL&mfpJZM^FL-$!XC)!*cUSYhRma`a_g!5u2&d9Ije?`akPp=_UnlqNoz+$=2k?~J2BBNu)Ei!I5zTHdTdh>72wjcSN zr{Pvbw4>;^F5fog73(bJt*~v$? z@0Hr^otqXt|Mv4Wm{D^O^==G;Q=qX3^Wrx@va)Z|7>}X|XudUh6?)q5*}*G(0>%3W zA{N{GJZDTKtsmFgDCKTrHBN2C1oFdqDQXM|a;p3_Ubqkqy-%^Sq4I6WAmyL7RTFJa zb(-l+ii7eq3}dV7>nGK$)+L`aYBoHfIQ2_u)|kv0&Z`*R-O;?+O_P zwlgyiy{fU57U}R@y;%PC+lu+3i=Yb`HuiPofSE1%Zw0QIxumBrS^kd~xr9)9Bsbw~B zcC{F-oL$lQc4bZFTo5^4rbY!xIZ_3=BJhGfKiF(y0e9F0qO(L#d!6B%FhQ5zU1y5i zS^H3qWps&)+U-M|%T5(2m=7Hh=HkrdAQAyJFIX=rerQ+(DLL?q0M$9v&Z|(yMuY!| zo;)7dM~Omq#YxP~BNY6SKx!s6XgoSljneauq5G_A6p~)UgX2H+ic7| zfiosble}DDv(fN+2mIbZN>!M|CA*z9G9@GuCvvNT$Av31QAszR@l(i%VKT94`gGL9 zDPqE!s4ax0@f|Nl3g$yKE2H1Not^Sfz9Th3Njx{i;A;QRWGi>goN8GE(a~Zp3Zdzd z+vvxBl@gdOY!EtDFz*;D?8^^~2+G>m*3pZ+CC|d@TJE#m{ItxE95_s-PKk-qk1d>K Qhe?4S>DqL>b-nNZ0Ir?qPyhe` literal 69623 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

    aHktG&!|zG>WHL6OaIgjvq~ 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 Date: Wed, 15 Jul 2026 08:09:32 +0000 Subject: [PATCH 345/369] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/app/components/svg-images/svg-images.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/svg-images/svg-images.component.html b/frontend/src/app/components/svg-images/svg-images.component.html index 90b625202..76711baa5 100644 --- a/frontend/src/app/components/svg-images/svg-images.component.html +++ b/frontend/src/app/components/svg-images/svg-images.component.html @@ -331,7 +331,7 @@ - + From 71703c75e0cb1ba4fc6d6330f2674acc83232032 Mon Sep 17 00:00:00 2001 From: natsoni Date: Thu, 16 Jul 2026 16:56:45 +0200 Subject: [PATCH 346/369] Footer logo links to mempool holdings --- .../components/global-footer/global-footer.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index fc493ab43..cf60774d4 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -7,10 +7,10 @@ @if (enterpriseInfo?.footer_img) { } @else { - + - + }

From 54752d320dab42f943c942a5ab96253a8622c9a5 Mon Sep 17 00:00:00 2001 From: wiz Date: Fri, 17 Jul 2026 15:47:24 +0900 Subject: [PATCH 347/369] Remove noopenner noreferrer from mempool holdings link --- .../components/global-footer/global-footer.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index cf60774d4..09bc9e2aa 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -7,7 +7,7 @@ @if (enterpriseInfo?.footer_img) { } @else { - + From 26c4b863320179f36c018fd1b8cfa9333099f1e5 Mon Sep 17 00:00:00 2001 From: wiz Date: Sat, 18 Jul 2026 00:08:36 +0900 Subject: [PATCH 348/369] ops: Bump elements to 23.3.2 in prod installer --- production/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/install b/production/install index e63f5e251..e49b4ff70 100755 --- a/production/install +++ b/production/install @@ -364,7 +364,7 @@ ELEMENTS_REPO_URL=https://github.com/ElementsProject/elements ELEMENTS_REPO_NAME=elements ELEMENTS_REPO_BRANCH=master #ELEMENTS_LATEST_RELEASE=$(curl -s https://api.github.com/repos/ElementsProject/elements/releases/latest|grep tag_name|head -1|cut -d '"' -f4) -ELEMENTS_LATEST_RELEASE=elements-23.3.0 +ELEMENTS_LATEST_RELEASE=elements-23.3.2 echo -n '.' BITCOIN_ELECTRS_REPO_URL=https://github.com/mempool/electrs From e999135cff6a0bdc2b655b832bfec9d07c712bb7 Mon Sep 17 00:00:00 2001 From: wiz Date: Sun, 19 Jul 2026 01:55:08 +0900 Subject: [PATCH 349/369] ops: Add moar servers --- production/bitcoin.conf | 42 +++++ production/elements.conf | 152 +++++++++++-------- production/mempool-config.liquid.json | 54 ++++--- production/mempool-config.liquidtestnet.json | 54 ++++--- production/mempool-config.mainnet.json | 24 ++- production/mempool-config.signet.json | 54 ++++--- production/mempool-config.testnet.json | 54 ++++--- production/mempool-config.testnet4.json | 54 ++++--- 8 files changed, 327 insertions(+), 161 deletions(-) diff --git a/production/bitcoin.conf b/production/bitcoin.conf index 134e4af17..eaba4757e 100644 --- a/production/bitcoin.conf +++ b/production/bitcoin.conf @@ -57,6 +57,14 @@ zmqpubrawtx=tcp://127.0.0.1:8335 #addnode=[2401:b140:3::92:204]:8333 #addnode=[2401:b140:3::92:205]:8333 #addnode=[2401:b140:3::92:206]:8333 +#addnode=[2401:b140:3::92:207]:8333 +#addnode=[2401:b140:3::92:208]:8333 +#addnode=[2401:b140:3::92:209]:8333 +#addnode=[2401:b140:3::92:210]:8333 +#addnode=[2401:b140:3::92:211]:8333 +#addnode=[2401:b140:3::92:212]:8333 +#addnode=[2401:b140:3::92:213]:8333 +#addnode=[2401:b140:3::92:214]:8333 #addnode=[2401:b140:4::92:201]:8333 #addnode=[2401:b140:4::92:202]:8333 #addnode=[2401:b140:4::92:203]:8333 @@ -69,10 +77,14 @@ zmqpubrawtx=tcp://127.0.0.1:8335 #addnode=[2401:b140:4::92:210]:8333 #addnode=[2401:b140:4::92:211]:8333 #addnode=[2401:b140:4::92:212]:8333 +#addnode=[2401:b140:4::92:213]:8333 +#addnode=[2401:b140:4::92:214]:8333 #addnode=[2401:b140:5::92:201]:8333 #addnode=[2401:b140:5::92:202]:8333 #addnode=[2401:b140:5::92:203]:8333 #addnode=[2401:b140:5::92:204]:8333 +#addnode=[2401:b140:6::92:201]:8333 +#addnode=[2401:b140:6::92:202]:8333 [test] daemon=1 @@ -112,6 +124,14 @@ zmqpubrawtx=tcp://127.0.0.1:18335 #addnode=[2401:b140:3::92:204]:18333 #addnode=[2401:b140:3::92:205]:18333 #addnode=[2401:b140:3::92:206]:18333 +#addnode=[2401:b140:3::92:207]:18333 +#addnode=[2401:b140:3::92:208]:18333 +#addnode=[2401:b140:3::92:209]:18333 +#addnode=[2401:b140:3::92:210]:18333 +#addnode=[2401:b140:3::92:211]:18333 +#addnode=[2401:b140:3::92:212]:18333 +#addnode=[2401:b140:3::92:213]:18333 +#addnode=[2401:b140:3::92:214]:18333 #addnode=[2401:b140:4::92:201]:18333 #addnode=[2401:b140:4::92:202]:18333 #addnode=[2401:b140:4::92:203]:18333 @@ -128,6 +148,8 @@ zmqpubrawtx=tcp://127.0.0.1:18335 #addnode=[2401:b140:5::92:202]:18333 #addnode=[2401:b140:5::92:203]:18333 #addnode=[2401:b140:5::92:204]:18333 +#addnode=[2401:b140:6::92:201]:18333 +#addnode=[2401:b140:6::92:202]:18333 [signet] daemon=1 @@ -167,6 +189,14 @@ zmqpubrawtx=tcp://127.0.0.1:38335 #addnode=[2401:b140:3::92:204]:38333 #addnode=[2401:b140:3::92:205]:38333 #addnode=[2401:b140:3::92:206]:38333 +#addnode=[2401:b140:3::92:207]:38333 +#addnode=[2401:b140:3::92:208]:38333 +#addnode=[2401:b140:3::92:209]:38333 +#addnode=[2401:b140:3::92:210]:38333 +#addnode=[2401:b140:3::92:211]:38333 +#addnode=[2401:b140:3::92:212]:38333 +#addnode=[2401:b140:3::92:213]:38333 +#addnode=[2401:b140:3::92:214]:38333 #addnode=[2401:b140:4::92:201]:38333 #addnode=[2401:b140:4::92:202]:38333 #addnode=[2401:b140:4::92:203]:38333 @@ -185,6 +215,8 @@ zmqpubrawtx=tcp://127.0.0.1:38335 #addnode=[2401:b140:5::92:202]:38333 #addnode=[2401:b140:5::92:203]:38333 #addnode=[2401:b140:5::92:204]:38333 +#addnode=[2401:b140:6::92:201]:38333 +#addnode=[2401:b140:6::92:202]:38333 [testnet4] daemon=1 @@ -224,6 +256,14 @@ zmqpubrawtx=tcp://127.0.0.1:48335 #addnode=[2401:b140:3::92:204]:48333 #addnode=[2401:b140:3::92:205]:48333 #addnode=[2401:b140:3::92:206]:48333 +#addnode=[2401:b140:3::92:207]:48333 +#addnode=[2401:b140:3::92:208]:48333 +#addnode=[2401:b140:3::92:209]:48333 +#addnode=[2401:b140:3::92:210]:48333 +#addnode=[2401:b140:3::92:211]:48333 +#addnode=[2401:b140:3::92:212]:48333 +#addnode=[2401:b140:3::92:213]:48333 +#addnode=[2401:b140:3::92:214]:48333 #addnode=[2401:b140:4::92:201]:48333 #addnode=[2401:b140:4::92:202]:48333 #addnode=[2401:b140:4::92:203]:48333 @@ -242,3 +282,5 @@ zmqpubrawtx=tcp://127.0.0.1:48335 #addnode=[2401:b140:5::92:202]:48333 #addnode=[2401:b140:5::92:203]:48333 #addnode=[2401:b140:5::92:204]:48333 +#addnode=[2401:b140:6::92:201]:48333 +#addnode=[2401:b140:6::92:202]:48333 diff --git a/production/elements.conf b/production/elements.conf index dcbb1856c..1d9f5ba0e 100644 --- a/production/elements.conf +++ b/production/elements.conf @@ -11,38 +11,38 @@ txindex=1 [liquidv1] validatepegin=1 mainchainrpcport=8332 -#addnode=[2401:b140::92:201]:7042 -#addnode=[2401:b140::92:202]:7042 -#addnode=[2401:b140::92:203]:7042 -#addnode=[2401:b140::92:204]:7042 -#addnode=[2401:b140:1::92:201]:7042 -#addnode=[2401:b140:1::92:202]:7042 -#addnode=[2401:b140:1::92:203]:7042 -#addnode=[2401:b140:1::92:204]:7042 -#addnode=[2401:b140:1::92:205]:7042 -#addnode=[2401:b140:1::92:206]:7042 -#addnode=[2401:b140:2::92:201]:7042 -#addnode=[2401:b140:2::92:202]:7042 -#addnode=[2401:b140:2::92:203]:7042 -#addnode=[2401:b140:2::92:204]:7042 -#addnode=[2401:b140:2::92:205]:7042 -#addnode=[2401:b140:2::92:206]:7042 -#addnode=[2401:b140:3::92:201]:7042 -#addnode=[2401:b140:3::92:202]:7042 -#addnode=[2401:b140:3::92:203]:7042 -#addnode=[2401:b140:3::92:204]:7042 -#addnode=[2401:b140:3::92:205]:7042 -#addnode=[2401:b140:3::92:206]:7042 -#addnode=[2401:b140:4::92:201]:7042 -#addnode=[2401:b140:4::92:202]:7042 -#addnode=[2401:b140:4::92:203]:7042 -#addnode=[2401:b140:4::92:204]:7042 -#addnode=[2401:b140:4::92:205]:7042 -#addnode=[2401:b140:4::92:206]:7042 -#addnode=[2401:b140:5::92:201]:7042 -#addnode=[2401:b140:5::92:202]:7042 -#addnode=[2401:b140:5::92:203]:7042 -#addnode=[2401:b140:5::92:204]:7042 +addnode=[2401:b140::92:201]:7042 +addnode=[2401:b140::92:202]:7042 +addnode=[2401:b140::92:203]:7042 +addnode=[2401:b140::92:204]:7042 +addnode=[2401:b140:1::92:201]:7042 +addnode=[2401:b140:1::92:202]:7042 +addnode=[2401:b140:1::92:203]:7042 +addnode=[2401:b140:1::92:204]:7042 +addnode=[2401:b140:1::92:205]:7042 +addnode=[2401:b140:1::92:206]:7042 +addnode=[2401:b140:2::92:201]:7042 +addnode=[2401:b140:2::92:202]:7042 +addnode=[2401:b140:2::92:203]:7042 +addnode=[2401:b140:2::92:204]:7042 +addnode=[2401:b140:2::92:205]:7042 +addnode=[2401:b140:2::92:206]:7042 +addnode=[2401:b140:3::92:201]:7042 +addnode=[2401:b140:3::92:202]:7042 +addnode=[2401:b140:3::92:203]:7042 +addnode=[2401:b140:3::92:204]:7042 +addnode=[2401:b140:3::92:205]:7042 +addnode=[2401:b140:3::92:206]:7042 +addnode=[2401:b140:4::92:201]:7042 +addnode=[2401:b140:4::92:202]:7042 +addnode=[2401:b140:4::92:203]:7042 +addnode=[2401:b140:4::92:204]:7042 +addnode=[2401:b140:4::92:205]:7042 +addnode=[2401:b140:4::92:206]:7042 +addnode=[2401:b140:5::92:201]:7042 +addnode=[2401:b140:5::92:202]:7042 +addnode=[2401:b140:5::92:203]:7042 +addnode=[2401:b140:5::92:204]:7042 [liquidtestnet] rpcport=7040 @@ -66,35 +66,59 @@ signblockscript=51210217e403ddb181872c32a0cd468c710040b2f53d8cac69f18dad07985ee3 evbparams=dynafed:0::: addnode=liquid-testnet.blockstream.com:18892 addnode=liquidtestnet.com:18891 -#addnode=[2401:b140::92:201]:18891 -#addnode=[2401:b140::92:202]:18891 -#addnode=[2401:b140::92:203]:18891 -#addnode=[2401:b140::92:204]:18891 -#addnode=[2401:b140:1::92:201]:18891 -#addnode=[2401:b140:1::92:202]:18891 -#addnode=[2401:b140:1::92:203]:18891 -#addnode=[2401:b140:1::92:204]:18891 -#addnode=[2401:b140:1::92:205]:18891 -#addnode=[2401:b140:1::92:206]:18891 -#addnode=[2401:b140:2::92:201]:18891 -#addnode=[2401:b140:2::92:202]:18891 -#addnode=[2401:b140:2::92:203]:18891 -#addnode=[2401:b140:2::92:204]:18891 -#addnode=[2401:b140:2::92:205]:18891 -#addnode=[2401:b140:2::92:206]:18891 -#addnode=[2401:b140:3::92:201]:18891 -#addnode=[2401:b140:3::92:202]:18891 -#addnode=[2401:b140:3::92:203]:18891 -#addnode=[2401:b140:3::92:204]:18891 -#addnode=[2401:b140:3::92:205]:18891 -#addnode=[2401:b140:3::92:206]:18891 -#addnode=[2401:b140:4::92:201]:18891 -#addnode=[2401:b140:4::92:202]:18891 -#addnode=[2401:b140:4::92:203]:18891 -#addnode=[2401:b140:4::92:204]:18891 -#addnode=[2401:b140:4::92:205]:18891 -#addnode=[2401:b140:4::92:206]:18891 -#addnode=[2401:b140:5::92:201]:18891 -#addnode=[2401:b140:5::92:202]:18891 -#addnode=[2401:b140:5::92:203]:18891 -#addnode=[2401:b140:5::92:204]:18891 +addnode=[2401:b140::92:201]:18891 +addnode=[2401:b140::92:202]:18891 +addnode=[2401:b140::92:203]:18891 +addnode=[2401:b140::92:204]:18891 +addnode=[2401:b140:1::92:201]:18891 +addnode=[2401:b140:1::92:202]:18891 +addnode=[2401:b140:1::92:203]:18891 +addnode=[2401:b140:1::92:204]:18891 +addnode=[2401:b140:1::92:205]:18891 +addnode=[2401:b140:1::92:206]:18891 +addnode=[2401:b140:2::92:201]:18891 +addnode=[2401:b140:2::92:202]:18891 +addnode=[2401:b140:2::92:203]:18891 +addnode=[2401:b140:2::92:204]:18891 +addnode=[2401:b140:2::92:205]:18891 +addnode=[2401:b140:2::92:206]:18891 +addnode=[2401:b140:2::92:207]:18891 +addnode=[2401:b140:2::92:208]:18891 +addnode=[2401:b140:2::92:209]:18891 +addnode=[2401:b140:2::92:210]:18891 +addnode=[2401:b140:2::92:211]:18891 +addnode=[2401:b140:2::92:212]:18891 +addnode=[2401:b140:2::92:213]:18891 +addnode=[2401:b140:2::92:214]:18891 +addnode=[2401:b140:3::92:201]:18891 +addnode=[2401:b140:3::92:202]:18891 +addnode=[2401:b140:3::92:203]:18891 +addnode=[2401:b140:3::92:204]:18891 +addnode=[2401:b140:3::92:205]:18891 +addnode=[2401:b140:3::92:206]:18891 +addnode=[2401:b140:3::92:207]:18891 +addnode=[2401:b140:3::92:208]:18891 +addnode=[2401:b140:3::92:209]:18891 +addnode=[2401:b140:3::92:210]:18891 +addnode=[2401:b140:3::92:211]:18891 +addnode=[2401:b140:3::92:212]:18891 +addnode=[2401:b140:3::92:213]:18891 +addnode=[2401:b140:3::92:214]:18891 +addnode=[2401:b140:4::92:201]:18891 +addnode=[2401:b140:4::92:202]:18891 +addnode=[2401:b140:4::92:203]:18891 +addnode=[2401:b140:4::92:204]:18891 +addnode=[2401:b140:4::92:205]:18891 +addnode=[2401:b140:4::92:206]:18891 +addnode=[2401:b140:4::92:207]:18891 +addnode=[2401:b140:4::92:208]:18891 +addnode=[2401:b140:4::92:209]:18891 +addnode=[2401:b140:4::92:210]:18891 +addnode=[2401:b140:4::92:211]:18891 +addnode=[2401:b140:4::92:212]:18891 +addnode=[2401:b140:4::92:213]:18891 +addnode=[2401:b140:4::92:214]:18891 +addnode=[2401:b140:5::92:201]:18891 +addnode=[2401:b140:5::92:202]:18891 +addnode=[2401:b140:5::92:203]:18891 +addnode=[2401:b140:5::92:204]:18891 diff --git a/production/mempool-config.liquid.json b/production/mempool-config.liquid.json index 0a8f6384f..6f72c363c 100644 --- a/production/mempool-config.liquid.json +++ b/production/mempool-config.liquid.json @@ -31,24 +31,12 @@ "http://node202.hnl.mempool.space:3001", "http://node203.hnl.mempool.space:3001", "http://node204.hnl.mempool.space:3001", - "http://node201.sg1.mempool.space:3001", - "http://node202.sg1.mempool.space:3001", - "http://node203.sg1.mempool.space:3001", - "http://node204.sg1.mempool.space:3001", - "http://node201.va1.mempool.space:3001", - "http://node202.va1.mempool.space:3001", - "http://node203.va1.mempool.space:3001", - "http://node204.va1.mempool.space:3001", - "http://node205.va1.mempool.space:3001", - "http://node206.va1.mempool.space:3001", - "http://node207.va1.mempool.space:3001", - "http://node208.va1.mempool.space:3001", - "http://node209.va1.mempool.space:3001", - "http://node210.va1.mempool.space:3001", - "http://node211.va1.mempool.space:3001", - "http://node212.va1.mempool.space:3001", - "http://node213.va1.mempool.space:3001", - "http://node214.va1.mempool.space:3001", + "http://node201.sv1.mempool.space:3001", + "http://node202.sv1.mempool.space:3001", + "http://node203.sv1.mempool.space:3001", + "http://node204.sv1.mempool.space:3001", + "http://node205.sv1.mempool.space:3001", + "http://node206.sv1.mempool.space:3001", "http://node201.fra.mempool.space:3001", "http://node202.fra.mempool.space:3001", "http://node203.fra.mempool.space:3001", @@ -68,7 +56,35 @@ "http://node203.tk7.mempool.space:3001", "http://node204.tk7.mempool.space:3001", "http://node205.tk7.mempool.space:3001", - "http://node206.tk7.mempool.space:3001" + "http://node206.tk7.mempool.space:3001", + "http://node207.tk7.mempool.space:3001", + "http://node208.tk7.mempool.space:3001", + "http://node209.tk7.mempool.space:3001", + "http://node210.tk7.mempool.space:3001", + "http://node211.tk7.mempool.space:3001", + "http://node212.tk7.mempool.space:3001", + "http://node213.tk7.mempool.space:3001", + "http://node214.tk7.mempool.space:3001", + "http://node201.va1.mempool.space:3001", + "http://node202.va1.mempool.space:3001", + "http://node203.va1.mempool.space:3001", + "http://node204.va1.mempool.space:3001", + "http://node205.va1.mempool.space:3001", + "http://node206.va1.mempool.space:3001", + "http://node207.va1.mempool.space:3001", + "http://node208.va1.mempool.space:3001", + "http://node209.va1.mempool.space:3001", + "http://node210.va1.mempool.space:3001", + "http://node211.va1.mempool.space:3001", + "http://node212.va1.mempool.space:3001", + "http://node213.va1.mempool.space:3001", + "http://node214.va1.mempool.space:3001", + "http://node201.sg1.mempool.space:3001", + "http://node202.sg1.mempool.space:3001", + "http://node203.sg1.mempool.space:3001", + "http://node204.sg1.mempool.space:3001", + "http://node201.es1.mempool.space:3001", + "http://node202.es1.mempool.space:3001" ] }, "DATABASE": { diff --git a/production/mempool-config.liquidtestnet.json b/production/mempool-config.liquidtestnet.json index ad3e5ebbd..5c615def5 100644 --- a/production/mempool-config.liquidtestnet.json +++ b/production/mempool-config.liquidtestnet.json @@ -31,24 +31,12 @@ "http://node202.hnl.mempool.space:3004", "http://node203.hnl.mempool.space:3004", "http://node204.hnl.mempool.space:3004", - "http://node201.sg1.mempool.space:3004", - "http://node202.sg1.mempool.space:3004", - "http://node203.sg1.mempool.space:3004", - "http://node204.sg1.mempool.space:3004", - "http://node201.va1.mempool.space:3004", - "http://node202.va1.mempool.space:3004", - "http://node203.va1.mempool.space:3004", - "http://node204.va1.mempool.space:3004", - "http://node205.va1.mempool.space:3004", - "http://node206.va1.mempool.space:3004", - "http://node207.va1.mempool.space:3004", - "http://node208.va1.mempool.space:3004", - "http://node209.va1.mempool.space:3004", - "http://node210.va1.mempool.space:3004", - "http://node211.va1.mempool.space:3004", - "http://node212.va1.mempool.space:3004", - "http://node213.va1.mempool.space:3004", - "http://node214.va1.mempool.space:3004", + "http://node201.sv1.mempool.space:3004", + "http://node202.sv1.mempool.space:3004", + "http://node203.sv1.mempool.space:3004", + "http://node204.sv1.mempool.space:3004", + "http://node205.sv1.mempool.space:3004", + "http://node206.sv1.mempool.space:3004", "http://node201.fra.mempool.space:3004", "http://node202.fra.mempool.space:3004", "http://node203.fra.mempool.space:3004", @@ -68,7 +56,35 @@ "http://node203.tk7.mempool.space:3004", "http://node204.tk7.mempool.space:3004", "http://node205.tk7.mempool.space:3004", - "http://node206.tk7.mempool.space:3004" + "http://node206.tk7.mempool.space:3004", + "http://node207.tk7.mempool.space:3004", + "http://node208.tk7.mempool.space:3004", + "http://node209.tk7.mempool.space:3004", + "http://node210.tk7.mempool.space:3004", + "http://node211.tk7.mempool.space:3004", + "http://node212.tk7.mempool.space:3004", + "http://node213.tk7.mempool.space:3004", + "http://node214.tk7.mempool.space:3004", + "http://node201.va1.mempool.space:3004", + "http://node202.va1.mempool.space:3004", + "http://node203.va1.mempool.space:3004", + "http://node204.va1.mempool.space:3004", + "http://node205.va1.mempool.space:3004", + "http://node206.va1.mempool.space:3004", + "http://node207.va1.mempool.space:3004", + "http://node208.va1.mempool.space:3004", + "http://node209.va1.mempool.space:3004", + "http://node210.va1.mempool.space:3004", + "http://node211.va1.mempool.space:3004", + "http://node212.va1.mempool.space:3004", + "http://node213.va1.mempool.space:3004", + "http://node214.va1.mempool.space:3004", + "http://node201.sg1.mempool.space:3004", + "http://node202.sg1.mempool.space:3004", + "http://node203.sg1.mempool.space:3004", + "http://node204.sg1.mempool.space:3004", + "http://node201.es1.mempool.space:3004", + "http://node202.es1.mempool.space:3004" ] }, "DATABASE": { diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index ee786891c..7ebbf66e2 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -75,6 +75,14 @@ "http://node204.tk7.mempool.space:3000", "http://node205.tk7.mempool.space:3000", "http://node206.tk7.mempool.space:3000", + "http://node207.tk7.mempool.space:3000", + "http://node208.tk7.mempool.space:3000", + "http://node209.tk7.mempool.space:3000", + "http://node210.tk7.mempool.space:3000", + "http://node211.tk7.mempool.space:3000", + "http://node212.tk7.mempool.space:3000", + "http://node213.tk7.mempool.space:3000", + "http://node214.tk7.mempool.space:3000", "http://node201.va1.mempool.space:3000", "http://node202.va1.mempool.space:3000", "http://node203.va1.mempool.space:3000", @@ -92,7 +100,9 @@ "http://node201.sg1.mempool.space:3000", "http://node202.sg1.mempool.space:3000", "http://node203.sg1.mempool.space:3000", - "http://node204.sg1.mempool.space:3000" + "http://node204.sg1.mempool.space:3000", + "http://node201.es1.mempool.space:3000", + "http://node202.es1.mempool.space:3000" ] }, "DATABASE": { @@ -144,6 +154,14 @@ "node204.tk7.mempool.space", "node205.tk7.mempool.space", "node206.tk7.mempool.space", + "node207.tk7.mempool.space", + "node208.tk7.mempool.space", + "node209.tk7.mempool.space", + "node210.tk7.mempool.space", + "node211.tk7.mempool.space", + "node212.tk7.mempool.space", + "node213.tk7.mempool.space", + "node214.tk7.mempool.space", "node201.va1.mempool.space", "node202.va1.mempool.space", "node203.va1.mempool.space", @@ -161,7 +179,9 @@ "node201.sg1.mempool.space", "node202.sg1.mempool.space", "node203.sg1.mempool.space", - "node204.sg1.mempool.space" + "node204.sg1.mempool.space", + "node201.es1.mempool.space", + "node202.es1.mempool.space" ] }, "REDIS": { diff --git a/production/mempool-config.signet.json b/production/mempool-config.signet.json index 568745e80..691e39ece 100644 --- a/production/mempool-config.signet.json +++ b/production/mempool-config.signet.json @@ -36,24 +36,12 @@ "http://node202.hnl.mempool.space:3003", "http://node203.hnl.mempool.space:3003", "http://node204.hnl.mempool.space:3003", - "http://node201.sg1.mempool.space:3003", - "http://node202.sg1.mempool.space:3003", - "http://node203.sg1.mempool.space:3003", - "http://node204.sg1.mempool.space:3003", - "http://node201.va1.mempool.space:3003", - "http://node202.va1.mempool.space:3003", - "http://node203.va1.mempool.space:3003", - "http://node204.va1.mempool.space:3003", - "http://node205.va1.mempool.space:3003", - "http://node206.va1.mempool.space:3003", - "http://node207.va1.mempool.space:3003", - "http://node208.va1.mempool.space:3003", - "http://node209.va1.mempool.space:3003", - "http://node210.va1.mempool.space:3003", - "http://node211.va1.mempool.space:3003", - "http://node212.va1.mempool.space:3003", - "http://node213.va1.mempool.space:3003", - "http://node214.va1.mempool.space:3003", + "http://node201.sv1.mempool.space:3003", + "http://node202.sv1.mempool.space:3003", + "http://node203.sv1.mempool.space:3003", + "http://node204.sv1.mempool.space:3003", + "http://node205.sv1.mempool.space:3003", + "http://node206.sv1.mempool.space:3003", "http://node201.fra.mempool.space:3003", "http://node202.fra.mempool.space:3003", "http://node203.fra.mempool.space:3003", @@ -73,7 +61,35 @@ "http://node203.tk7.mempool.space:3003", "http://node204.tk7.mempool.space:3003", "http://node205.tk7.mempool.space:3003", - "http://node206.tk7.mempool.space:3003" + "http://node206.tk7.mempool.space:3003", + "http://node207.tk7.mempool.space:3003", + "http://node208.tk7.mempool.space:3003", + "http://node209.tk7.mempool.space:3003", + "http://node210.tk7.mempool.space:3003", + "http://node211.tk7.mempool.space:3003", + "http://node212.tk7.mempool.space:3003", + "http://node213.tk7.mempool.space:3003", + "http://node214.tk7.mempool.space:3003", + "http://node201.va1.mempool.space:3003", + "http://node202.va1.mempool.space:3003", + "http://node203.va1.mempool.space:3003", + "http://node204.va1.mempool.space:3003", + "http://node205.va1.mempool.space:3003", + "http://node206.va1.mempool.space:3003", + "http://node207.va1.mempool.space:3003", + "http://node208.va1.mempool.space:3003", + "http://node209.va1.mempool.space:3003", + "http://node210.va1.mempool.space:3003", + "http://node211.va1.mempool.space:3003", + "http://node212.va1.mempool.space:3003", + "http://node213.va1.mempool.space:3003", + "http://node214.va1.mempool.space:3003", + "http://node201.sg1.mempool.space:3003", + "http://node202.sg1.mempool.space:3003", + "http://node203.sg1.mempool.space:3003", + "http://node204.sg1.mempool.space:3003", + "http://node201.es1.mempool.space:3003", + "http://node202.es1.mempool.space:3003" ] }, "DATABASE": { diff --git a/production/mempool-config.testnet.json b/production/mempool-config.testnet.json index 033a35178..5f917ace8 100644 --- a/production/mempool-config.testnet.json +++ b/production/mempool-config.testnet.json @@ -36,24 +36,12 @@ "http://node202.hnl.mempool.space:3002", "http://node203.hnl.mempool.space:3002", "http://node204.hnl.mempool.space:3002", - "http://node201.sg1.mempool.space:3002", - "http://node202.sg1.mempool.space:3002", - "http://node203.sg1.mempool.space:3002", - "http://node204.sg1.mempool.space:3002", - "http://node201.va1.mempool.space:3002", - "http://node202.va1.mempool.space:3002", - "http://node203.va1.mempool.space:3002", - "http://node204.va1.mempool.space:3002", - "http://node205.va1.mempool.space:3002", - "http://node206.va1.mempool.space:3002", - "http://node207.va1.mempool.space:3002", - "http://node208.va1.mempool.space:3002", - "http://node209.va1.mempool.space:3002", - "http://node210.va1.mempool.space:3002", - "http://node211.va1.mempool.space:3002", - "http://node212.va1.mempool.space:3002", - "http://node213.va1.mempool.space:3002", - "http://node214.va1.mempool.space:3002", + "http://node201.sv1.mempool.space:3002", + "http://node202.sv1.mempool.space:3002", + "http://node203.sv1.mempool.space:3002", + "http://node204.sv1.mempool.space:3002", + "http://node205.sv1.mempool.space:3002", + "http://node206.sv1.mempool.space:3002", "http://node201.fra.mempool.space:3002", "http://node202.fra.mempool.space:3002", "http://node203.fra.mempool.space:3002", @@ -73,7 +61,35 @@ "http://node203.tk7.mempool.space:3002", "http://node204.tk7.mempool.space:3002", "http://node205.tk7.mempool.space:3002", - "http://node206.tk7.mempool.space:3002" + "http://node206.tk7.mempool.space:3002", + "http://node207.tk7.mempool.space:3002", + "http://node208.tk7.mempool.space:3002", + "http://node209.tk7.mempool.space:3002", + "http://node210.tk7.mempool.space:3002", + "http://node211.tk7.mempool.space:3002", + "http://node212.tk7.mempool.space:3002", + "http://node213.tk7.mempool.space:3002", + "http://node214.tk7.mempool.space:3002", + "http://node201.va1.mempool.space:3002", + "http://node202.va1.mempool.space:3002", + "http://node203.va1.mempool.space:3002", + "http://node204.va1.mempool.space:3002", + "http://node205.va1.mempool.space:3002", + "http://node206.va1.mempool.space:3002", + "http://node207.va1.mempool.space:3002", + "http://node208.va1.mempool.space:3002", + "http://node209.va1.mempool.space:3002", + "http://node210.va1.mempool.space:3002", + "http://node211.va1.mempool.space:3002", + "http://node212.va1.mempool.space:3002", + "http://node213.va1.mempool.space:3002", + "http://node214.va1.mempool.space:3002", + "http://node201.sg1.mempool.space:3002", + "http://node202.sg1.mempool.space:3002", + "http://node203.sg1.mempool.space:3002", + "http://node204.sg1.mempool.space:3002", + "http://node201.es1.mempool.space:3002", + "http://node202.es1.mempool.space:3002" ] }, "DATABASE": { diff --git a/production/mempool-config.testnet4.json b/production/mempool-config.testnet4.json index ec47c7875..4dcec6605 100644 --- a/production/mempool-config.testnet4.json +++ b/production/mempool-config.testnet4.json @@ -36,24 +36,12 @@ "http://node202.hnl.mempool.space:3005", "http://node203.hnl.mempool.space:3005", "http://node204.hnl.mempool.space:3005", - "http://node201.sg1.mempool.space:3005", - "http://node202.sg1.mempool.space:3005", - "http://node203.sg1.mempool.space:3005", - "http://node204.sg1.mempool.space:3005", - "http://node201.va1.mempool.space:3005", - "http://node202.va1.mempool.space:3005", - "http://node203.va1.mempool.space:3005", - "http://node204.va1.mempool.space:3005", - "http://node205.va1.mempool.space:3005", - "http://node206.va1.mempool.space:3005", - "http://node207.va1.mempool.space:3005", - "http://node208.va1.mempool.space:3005", - "http://node209.va1.mempool.space:3005", - "http://node210.va1.mempool.space:3005", - "http://node211.va1.mempool.space:3005", - "http://node212.va1.mempool.space:3005", - "http://node213.va1.mempool.space:3005", - "http://node214.va1.mempool.space:3005", + "http://node201.sv1.mempool.space:3005", + "http://node202.sv1.mempool.space:3005", + "http://node203.sv1.mempool.space:3005", + "http://node204.sv1.mempool.space:3005", + "http://node205.sv1.mempool.space:3005", + "http://node206.sv1.mempool.space:3005", "http://node201.fra.mempool.space:3005", "http://node202.fra.mempool.space:3005", "http://node203.fra.mempool.space:3005", @@ -73,7 +61,35 @@ "http://node203.tk7.mempool.space:3005", "http://node204.tk7.mempool.space:3005", "http://node205.tk7.mempool.space:3005", - "http://node206.tk7.mempool.space:3005" + "http://node206.tk7.mempool.space:3005", + "http://node207.tk7.mempool.space:3005", + "http://node208.tk7.mempool.space:3005", + "http://node209.tk7.mempool.space:3005", + "http://node210.tk7.mempool.space:3005", + "http://node211.tk7.mempool.space:3005", + "http://node212.tk7.mempool.space:3005", + "http://node213.tk7.mempool.space:3005", + "http://node214.tk7.mempool.space:3005", + "http://node201.va1.mempool.space:3005", + "http://node202.va1.mempool.space:3005", + "http://node203.va1.mempool.space:3005", + "http://node204.va1.mempool.space:3005", + "http://node205.va1.mempool.space:3005", + "http://node206.va1.mempool.space:3005", + "http://node207.va1.mempool.space:3005", + "http://node208.va1.mempool.space:3005", + "http://node209.va1.mempool.space:3005", + "http://node210.va1.mempool.space:3005", + "http://node211.va1.mempool.space:3005", + "http://node212.va1.mempool.space:3005", + "http://node213.va1.mempool.space:3005", + "http://node214.va1.mempool.space:3005", + "http://node201.sg1.mempool.space:3005", + "http://node202.sg1.mempool.space:3005", + "http://node203.sg1.mempool.space:3005", + "http://node204.sg1.mempool.space:3005", + "http://node201.es1.mempool.space:3005", + "http://node202.es1.mempool.space:3005" ] }, "DATABASE": { From c4e08bb867752e56f5f29cffd4ebd46c7157c461 Mon Sep 17 00:00:00 2001 From: natsoni Date: Sun, 19 Jul 2026 10:38:30 +0200 Subject: [PATCH 350/369] Force stratum block reward to BTC --- .../components/stratum/stratum-list/stratum-list.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html index 644916fec..571f3ac31 100644 --- a/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html +++ b/frontend/src/app/components/stratum/stratum-list/stratum-list.component.html @@ -41,7 +41,7 @@ {{ row.job.tag }} - + From 2feee1d2fd6609ba2104baf8d9dce997cde01871 Mon Sep 17 00:00:00 2001 From: jramos0 Date: Mon, 20 Jul 2026 23:18:18 -0600 Subject: [PATCH 351/369] Fix: aligned 'why is this block empty' --- frontend/src/app/components/block/block.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 7e5c7af72..2864cb4a5 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -110,7 +110,7 @@ -
+
Date: Fri, 24 Jul 2026 21:06:48 +0000 Subject: [PATCH 352/369] clean up stale unix sockets --- backend/src/index.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/backend/src/index.ts b/backend/src/index.ts index e857a377b..04690c4bc 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,5 +1,6 @@ import express from 'express'; import { Application, Request, Response, NextFunction } from 'express'; +import * as fs from 'fs'; import * as http from 'http'; import * as WebSocket from 'ws'; import bitcoinApi from './api/bitcoin/bitcoin-api-factory'; @@ -67,6 +68,10 @@ class Server { constructor() { this.app = express(); + if (cluster.isPrimary && config.MEMPOOL.UNIX_SOCKET_PATH) { + this.clearStaleUnixSocket(config.MEMPOOL.UNIX_SOCKET_PATH); + } + if (!config.MEMPOOL.SPAWN_CLUSTER_PROCS) { void this.startServer(); return; @@ -249,6 +254,17 @@ class Server { void poolsUpdater.$startService(); } + clearStaleUnixSocket(path: string): void { + try { + if (fs.existsSync(path)) { + fs.unlinkSync(path); + logger.notice(`Removed stale unix socket ${path}`); + } + } catch (e) { + logger.err(`Failed to remove stale unix socket ${path}. Reason: ${e instanceof Error ? e.message : e}`); + } + } + /** @asyncSafe */ async runMainUpdateLoop(): Promise { const start = Date.now(); From 2c3af53943374f1d93eb06af9bf0a08ee7e4893c Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 27 Jul 2026 21:48:20 +0200 Subject: [PATCH 353/369] Revert "[ssr] accept 301 and 302 as valid for nginx cache" --- production/nginx/server-common.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/nginx/server-common.conf b/production/nginx/server-common.conf index c5af76fc3..0737f2718 100644 --- a/production/nginx/server-common.conf +++ b/production/nginx/server-common.conf @@ -217,7 +217,7 @@ location @mempool-space-ssr { proxy_cache_background_update on; proxy_cache_use_stale updating; proxy_cache unfurler; - proxy_cache_valid 200 301 302 5m; # will re-render page if older than this + proxy_cache_valid 200 5m; # will re-render page if older than this proxy_redirect off; expires 5m; From d74b0633c88b02c1b382bf6ce67ac3d416122b10 Mon Sep 17 00:00:00 2001 From: natsoni Date: Tue, 28 Jul 2026 20:19:29 +0200 Subject: [PATCH 354/369] Add sv2 logo to about page --- .../app/components/about/about.component.html | 5 +++++ .../app/components/about/about.component.scss | 7 +++++++ frontend/src/resources/profile/stratum-v2.png | Bin 0 -> 21054 bytes 3 files changed, 12 insertions(+) create mode 100644 frontend/src/resources/profile/stratum-v2.png diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index acc85d65b..1012d6823 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -711,6 +711,11 @@
+ + +
diff --git a/frontend/src/app/components/about/about.component.scss b/frontend/src/app/components/about/about.component.scss index 28aeda160..471cf828d 100644 --- a/frontend/src/app/components/about/about.component.scss +++ b/frontend/src/app/components/about/about.component.scss @@ -104,6 +104,7 @@ .alliances-logos { align-items: center; margin-bottom: 80px; + justify-content: center; } .alliance-logo { height: 80px; @@ -114,6 +115,12 @@ margin-top: 45px; } } + .stratum-v2-logo { + object-fit: contain; + @media (max-width: 767px) { + width: 300px; + } + } .copa { @media (min-width: 992px) { position: relative; diff --git a/frontend/src/resources/profile/stratum-v2.png b/frontend/src/resources/profile/stratum-v2.png new file mode 100644 index 0000000000000000000000000000000000000000..b792f8af72ddc11c4780b1002297222d5e826335 GIT binary patch literal 21054 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 Date: Sat, 1 Aug 2026 17:07:13 +0000 Subject: [PATCH 355/369] fix scheduleSingleTask --- backend/src/indexer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts index 46256cc29..427b20d18 100644 --- a/backend/src/indexer.ts +++ b/backend/src/indexer.ts @@ -113,12 +113,11 @@ class Indexer { } } this.tasksScheduled[task] = setTimeout(async () => { + delete this.tasksScheduled[task]; try { await this.runSingleTask(task); } catch (e) { logger.err(`Unexpected error in scheduled task ${task}: ` + (e instanceof Error ? e.message : e)); - } finally { - clearTimeout(this.tasksScheduled[task]); } }, timeout); } From 31c6b72f42f90f848e6cfbd189fc0d20096ab52d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 1 Aug 2026 22:06:07 +0200 Subject: [PATCH 356/369] fix code indentation --- backend/src/indexer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts index 427b20d18..ff10d701e 100644 --- a/backend/src/indexer.ts +++ b/backend/src/indexer.ts @@ -143,7 +143,8 @@ class Indexer { latestPriceId = await PricesRepository.$getLatestPriceId(); } catch (e) { logger.debug('failed to fetch latest price id from db: ' + (e instanceof Error ? e.message : e)); - } if (priceUpdater.historyInserted === false || latestPriceId === null) { + } + if (priceUpdater.historyInserted === false || latestPriceId === null) { logger.debug(`Blocks prices indexer is waiting for the price updater to complete`, logger.tags.mining); this.scheduleSingleTask(task, 10000); } else { From 35399957178c586e11f0e25037c449099cf1c279 Mon Sep 17 00:00:00 2001 From: natsoni Date: Tue, 4 Aug 2026 10:41:21 +0200 Subject: [PATCH 357/369] Fix page jump on mobile when scrolling to footer --- .../accelerations-list/accelerations-list.component.scss | 4 ++++ .../custom-dashboard/custom-dashboard.component.scss | 2 ++ .../federation-addresses-list.component.scss | 4 ++++ .../recent-pegs-list/recent-pegs-list.component.scss | 4 ++++ .../recent-transactions-list.component.scss | 2 ++ frontend/src/app/dashboard/dashboard.component.scss | 1 + 6 files changed, 17 insertions(+) diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss index 06e1ca48c..7a7d3f8d0 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss @@ -97,6 +97,10 @@ tr, td, th { } .widget { + .acceleration-list { + overflow-anchor: none; + } + .txid { width: 30%; overflow: hidden; diff --git a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.scss b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.scss index 4a9ffe94a..d1079621e 100644 --- a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.scss +++ b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.scss @@ -135,6 +135,7 @@ } .latest-transactions { + overflow-anchor: none; width: 100%; text-align: left; table-layout:fixed; @@ -223,6 +224,7 @@ } .lastest-replacements-table { + overflow-anchor: none; width: 100%; text-align: left; table-layout:fixed; diff --git a/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.scss b/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.scss index 33d7c8f78..ddbe1ad61 100644 --- a/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.scss +++ b/frontend/src/app/components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component.scss @@ -10,6 +10,10 @@ } } +.widget table { + overflow-anchor: none; +} + tr, td, th { border: 0px; padding-top: 0.65rem; diff --git a/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.scss b/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.scss index 62dd5c336..9baf129e4 100644 --- a/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.scss +++ b/frontend/src/app/components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component.scss @@ -4,6 +4,10 @@ margin-top: 13px; } +.widget table { + overflow-anchor: none; +} + tr, td, th { border: 0px; padding-top: 0.65rem; diff --git a/frontend/src/app/components/recent-transactions-list/recent-transactions-list.component.scss b/frontend/src/app/components/recent-transactions-list/recent-transactions-list.component.scss index 41622c43e..936d251a3 100644 --- a/frontend/src/app/components/recent-transactions-list/recent-transactions-list.component.scss +++ b/frontend/src/app/components/recent-transactions-list/recent-transactions-list.component.scss @@ -93,6 +93,8 @@ } .widget .latest-transactions { + overflow-anchor: none; + .table-cell-satoshis { @media (min-width: 768px) { display: none; diff --git a/frontend/src/app/dashboard/dashboard.component.scss b/frontend/src/app/dashboard/dashboard.component.scss index 0ea68475c..6c32feb4b 100644 --- a/frontend/src/app/dashboard/dashboard.component.scss +++ b/frontend/src/app/dashboard/dashboard.component.scss @@ -180,6 +180,7 @@ } .lastest-replacements-table { + overflow-anchor: none; width: 100%; text-align: left; table-layout:fixed; From e31299c4e91ff6d0dc1a51eb76aad282122e7069 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Wed, 5 Aug 2026 05:54:08 +0900 Subject: [PATCH 358/369] Only sync svgs from the mining pool logos repo --- frontend/sync-assets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index f0797b563..a77b99795 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -230,7 +230,7 @@ const downloadMiningPoolLogos = async () => { const poolLogos = await fetchGitHubContents('/repos/mempool/mining-pool-logos/contents/', !!config.githubToken); let downloadedCount = 0; - const validFiles = poolLogos.filter(item => item.type === 'file' && item.download_url); + const validFiles = poolLogos.filter(item => item.type === 'file' && item.download_url && item.name.toLowerCase().endsWith('.svg')); for (const poolLogo of validFiles) { if (config.verbose) { From b7e81e67e2c92d9254b4f57177b3dcc1feeac42f Mon Sep 17 00:00:00 2001 From: mononaut Date: Thu, 6 Aug 2026 11:36:00 +0000 Subject: [PATCH 359/369] optimize ln nodes sockets query --- backend/src/repositories/NodesSocketsRepository.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/repositories/NodesSocketsRepository.ts b/backend/src/repositories/NodesSocketsRepository.ts index 8ddfafe68..9e2d28549 100644 --- a/backend/src/repositories/NodesSocketsRepository.ts +++ b/backend/src/repositories/NodesSocketsRepository.ts @@ -30,12 +30,13 @@ class NodesSocketsRepository { return 0; } try { + const placeholders = addresses.map(() => '?').join(','); const query = ` DELETE FROM nodes_sockets WHERE public_key = ? - AND socket NOT IN (${addresses.map(id => `"${id}"`).join(',')}) + AND socket NOT IN (${placeholders}) `; - const [result] = await DB.query(query, [publicKey]); + const [result] = await DB.query(query, [publicKey, ...addresses]); return result.affectedRows; } catch (e) { logger.err(`Cannot delete unused sockets for ${publicKey} from db. Reason: ` + (e instanceof Error ? e.message : e)); From 38102aa387e45fce1b47913d6d59d1752a38fc59 Mon Sep 17 00:00:00 2001 From: natsoni Date: Fri, 7 Aug 2026 12:41:24 +0200 Subject: [PATCH 360/369] Fix Liquid URL from block height search --- frontend/src/app/components/block/block.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index b1b055957..698504ab8 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -226,7 +226,7 @@ export class BlockComponent implements OnInit, OnDestroy { switchMap((hash) => { this.blockHash = hash; this.location.replaceState( - this.router.createUrlTree([(this.network ? '/' + this.network : '') + '/block/', hash]).toString() + this.router.createUrlTree([this.relativeUrlPipe.transform('/block/'), hash]).toString() ); this.seoService.updateCanonical(this.location.path()); return this.apiService.getBlock$(hash).pipe( From 71f07e310aa9b8f2f1f8e8f93787192028ca0422 Mon Sep 17 00:00:00 2001 From: mononaut Date: Sat, 8 Aug 2026 01:58:41 +0000 Subject: [PATCH 361/369] remove matomo --- .../about/about-sponsors.component.html | 4 +- .../about/about-sponsors.component.ts | 14 --- .../app/components/about/about.component.ts | 12 --- .../accelerate-checkout.component.ts | 3 - .../privacy-policy.component.html | 2 +- .../components/tracker/tracker.component.ts | 2 - .../transaction/transaction.component.ts | 3 - .../src/app/services/enterprise.service.ts | 102 ------------------ 8 files changed, 3 insertions(+), 139 deletions(-) diff --git a/frontend/src/app/components/about/about-sponsors.component.html b/frontend/src/app/components/about/about-sponsors.component.html index 9471fc78f..02189acb6 100644 --- a/frontend/src/app/components/about/about-sponsors.component.html +++ b/frontend/src/app/components/about/about-sponsors.component.html @@ -1,14 +1,14 @@

If you're an individual...

- Become a Community Sponsor + Become a Community Sponsor

If you're a business...

- Become an Enterprise Sponsor + Become an Enterprise Sponsor diff --git a/frontend/src/app/components/about/about-sponsors.component.ts b/frontend/src/app/components/about/about-sponsors.component.ts index be86a9d93..673bb207c 100644 --- a/frontend/src/app/components/about/about-sponsors.component.ts +++ b/frontend/src/app/components/about/about-sponsors.component.ts @@ -1,5 +1,4 @@ import { Component, Input } from '@angular/core'; -import { EnterpriseService } from '@app/services/enterprise.service'; @Component({ selector: 'app-about-sponsors', @@ -10,17 +9,4 @@ import { EnterpriseService } from '@app/services/enterprise.service'; export class AboutSponsorsComponent { @Input() host = 'https://mempool.space'; @Input() context = 'about'; - - constructor(private enterpriseService: EnterpriseService) { - } - - onSponsorClick(e): boolean { - this.enterpriseService.goal(5); - return true; - } - - onEnterpriseClick(e): boolean { - this.enterpriseService.goal(6); - return true; - } } diff --git a/frontend/src/app/components/about/about.component.ts b/frontend/src/app/components/about/about.component.ts index 75b4cbbe0..211f21622 100644 --- a/frontend/src/app/components/about/about.component.ts +++ b/frontend/src/app/components/about/about.component.ts @@ -10,7 +10,6 @@ import { Router, ActivatedRoute } from '@angular/router'; import { map, share, tap } from 'rxjs/operators'; import { ITranslators } from '@interfaces/node-api.interface'; import { DOCUMENT } from '@angular/common'; -import { EnterpriseService } from '@app/services/enterprise.service'; import { ThemeService } from '../../services/theme.service'; @Component({ @@ -40,7 +39,6 @@ export class AboutComponent implements OnInit { private seoService: SeoService, private ogService: OpenGraphService, public stateService: StateService, - private enterpriseService: EnterpriseService, private apiService: ApiService, private router: Router, private route: ActivatedRoute, @@ -141,16 +139,6 @@ export class AboutComponent implements OnInit { this.promoVideo.nativeElement.muted = false; } - onSponsorClick(e): boolean { - this.enterpriseService.goal(5); - return true; - } - - onEnterpriseClick(e): boolean { - this.enterpriseService.goal(6); - return true; - } - get isLightMode(): boolean { return this.loadedTheme === 'nymkappa'; } diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 6f8636e96..618e52fe8 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -9,7 +9,6 @@ import { ETA, EtaService } from '@app/services/eta.service'; import { Transaction } from '@interfaces/electrs.interface'; import { MiningStats } from '@app/services/mining.service'; import { IAuth, AuthServiceMempool } from '@app/services/auth.service'; -import { EnterpriseService } from '@app/services/enterprise.service'; import { PartnerCodeService } from '@app/services/partner-code.service'; import { ApiService } from '@app/services/api.service'; import { isDevMode } from '@angular/core'; @@ -147,7 +146,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { private audioService: AudioService, private cd: ChangeDetectorRef, private authService: AuthServiceMempool, - private enterpriseService: EnterpriseService, private partnerCodeService: PartnerCodeService, private themeService: ThemeService, ) { @@ -236,7 +234,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { if (this._step === 'checkout') { this.insertSquare(); - this.enterpriseService.goal(8); this.scrollToElementWithTimeout('acceleratePreviewAnchor', 'start', 100); } diff --git a/frontend/src/app/components/privacy-policy/privacy-policy.component.html b/frontend/src/app/components/privacy-policy/privacy-policy.component.html index 89d5a8394..3c4c13935 100644 --- a/frontend/src/app/components/privacy-policy/privacy-policy.component.html +++ b/frontend/src/app/components/privacy-policy/privacy-policy.component.html @@ -43,7 +43,7 @@
-
  • We use a self-hosted statistics application (matomo) for analytics purposes, which collects your IP address along with the requests you make. Our matomo instance is configured to respect your privacy by redacting your IP address and other methods, and we do not share this data with any third-party. To conceal your activity from our analytics, we recommend that you use a privacy protecting browser extension that blocks matomo from being loaded.
  • +
  • We use a self-hosted statistics application for analytics purposes, which collects your IP address along with the requests you make. Our instance is configured to respect your privacy by redacting your IP address and other methods, and we do not share this data with any third-party. To conceal your activity from our analytics, we recommend that you use a privacy protecting browser extension that blocks it from being loaded.
  • diff --git a/frontend/src/app/components/tracker/tracker.component.ts b/frontend/src/app/components/tracker/tracker.component.ts index 7ad2817a7..6195161ce 100644 --- a/frontend/src/app/components/tracker/tracker.component.ts +++ b/frontend/src/app/components/tracker/tracker.component.ts @@ -167,8 +167,6 @@ export class TrackerComponent implements OnInit, OnDestroy { this.miningStats = stats; }); - this.enterpriseService.page(); - this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => { this.enterpriseInfo = info; }); diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 8c7892c37..20ac47642 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -35,7 +35,6 @@ import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pip import { PriceService } from '@app/services/price.service'; import { isFeatureActive } from '@app/bitcoin.utils'; import { ServicesApiServices } from '@app/services/services-api.service'; -import { EnterpriseService } from '@app/services/enterprise.service'; import { PartnerCodeService } from '@app/services/partner-code.service'; import { ZONE_SERVICE } from '@app/injection-tokens'; import { MiningService, MiningStats } from '@app/services/mining.service'; @@ -217,7 +216,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { private seoService: SeoService, private priceService: PriceService, private storageService: StorageService, - private enterpriseService: EnterpriseService, private partnerCodeService: PartnerCodeService, private miningService: MiningService, private etaService: EtaService, @@ -228,7 +226,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { ngOnInit() { this.setupPartnerCode(); - this.enterpriseService.page(); this.isDetailsOpen = this.route.snapshot.queryParams['showDetails'] === 'true'; this.cpfpMode = this.route.snapshot.queryParams['cpfp'] === 'true'; diff --git a/frontend/src/app/services/enterprise.service.ts b/frontend/src/app/services/enterprise.service.ts index 350efa316..2dff81a20 100644 --- a/frontend/src/app/services/enterprise.service.ts +++ b/frontend/src/app/services/enterprise.service.ts @@ -3,7 +3,6 @@ import { Inject, Injectable } from '@angular/core'; import { ApiService } from '@app/services/api.service'; import { SeoService } from '@app/services/seo.service'; import { StateService } from '@app/services/state.service'; -import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; @Injectable({ @@ -12,8 +11,6 @@ import { BehaviorSubject } from 'rxjs'; export class EnterpriseService { exclusiveHostName = '.mempool.space'; subdomain: string | null = null; - statsUrl: string; - siteId: number; info$: BehaviorSubject = new BehaviorSubject(null); constructor( @@ -21,7 +18,6 @@ export class EnterpriseService { private apiService: ApiService, private seoService: SeoService, private stateService: StateService, - private activatedRoute: ActivatedRoute, ) { const subdomain = this.stateService.env.customize?.enterprise || this.document.location.hostname.indexOf(this.exclusiveHostName) > -1 && this.document.location.hostname.split(this.exclusiveHostName)[0] || false; @@ -30,8 +26,6 @@ export class EnterpriseService { this.fetchSubdomainInfo(); this.disableSubnetworks(); this.stateService.env.ACCELERATOR = false; - } else { - this.insertMatomo(); } } @@ -51,12 +45,10 @@ export class EnterpriseService { fetchSubdomainInfo(): void { if (this.stateService.env.customize?.branding) { const info = this.stateService.env.customize?.branding; - this.insertMatomo(info.site_id); this.seoService.setEnterpriseTitle(info.title, true); this.info$.next(this.processEnterpriseInfo(info)); } else { this.apiService.getEnterpriseInfo$(this.subdomain).subscribe((info) => { - this.insertMatomo(info.site_id); this.seoService.setEnterpriseTitle(info.title); this.info$.next(this.processEnterpriseInfo(info)); }, @@ -78,98 +70,4 @@ export class EnterpriseService { logoUrl, }; } - - insertMatomo(siteId?: number): void { - let statsUrl = '//stats.mempool.space/'; - - if (!siteId) { - switch (this.document.location.hostname) { - case 'mempool.space': - statsUrl = '//stats.mempool.space/'; - siteId = 5; - break; - case 'mempool.ninja': - statsUrl = '//stats.mempool.space/'; - siteId = 4; - break; - case 'liquid.network': - siteId = 8; - statsUrl = '//stats.liquid.network/'; - break; - case 'liquid.place': - siteId = 10; - statsUrl = '//stats.liquid.network/'; - break; - default: - return; - } - } - - this.statsUrl = statsUrl; - this.siteId = siteId; - - // @ts-ignore - if (window._paq && window['Matomo']) { - window['Matomo'].addTracker(statsUrl+'m.php', siteId.toString()); - const matomo = this.getMatomo(); - matomo.setDocumentTitle(this.seoService.getTitle()); - matomo.setCustomUrl(this.getCustomUrl()); - matomo.disableCookies(); - matomo.trackPageView(); - matomo.enableLinkTracking(); - } else { - // @ts-ignore - const alreadyInitialized = !!window._paq; - // @ts-ignore - const _paq = window._paq = window._paq || []; - _paq.push(['setDocumentTitle', this.seoService.getTitle()]); - _paq.push(['setCustomUrl', this.getCustomUrl()]); - _paq.push(['disableCookies']); - _paq.push(['trackPageView']); - _paq.push(['enableLinkTracking']); - if (alreadyInitialized) { - _paq.push(['addTracker', statsUrl+'m.php', siteId.toString()]); - } else { - (function() { - _paq.push(['setTrackerUrl', statsUrl+'m.php']); - _paq.push(['setSiteId', siteId.toString()]); - const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; - // @ts-ignore - g.type='text/javascript'; g.async=true; g.src=statsUrl+'m.js'; s.parentNode.insertBefore(g,s); - })(); - } - } - } - - private getMatomo() { - if (this.siteId != null) { - return window['Matomo']?.getTracker(this.statsUrl+'m.php', this.siteId); - } - } - - goal(id: number) { - // @ts-ignore - this.getMatomo()?.trackGoal(id); - } - - page() { - const matomo = this.getMatomo(); - if (matomo) { - matomo.setCustomUrl(this.getCustomUrl()); - matomo.trackPageView(); - } - } - - private getCustomUrl(): string { - let url = window.location.origin + '/'; - let route = this.activatedRoute; - while (route) { - const segment = route?.routeConfig?.path; - if (segment && segment.length) { - url += segment + '/'; - } - route = route.firstChild; - } - return url; - } } From 7c4f236cb07d263f81676876a1ba0f51fcaeaa47 Mon Sep 17 00:00:00 2001 From: rodribp Date: Mon, 10 Aug 2026 01:43:46 -0600 Subject: [PATCH 362/369] add: flag_values table and repository --- backend/src/api/database-migration.ts | 19 ++- .../src/repositories/FlagValueRepository.ts | 133 ++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 backend/src/repositories/FlagValueRepository.ts diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 3932179ed..39bd7787f 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository'; import { RowDataPacket } from 'mysql2'; class DatabaseMigration { - private static currentVersion = 111; + private static currentVersion = 112; private queryTimeout = 3600_000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -1255,6 +1255,11 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `compact_cpfp_clusters` ADD template_algo TINYINT UNSIGNED NOT NULL DEFAULT 0'); await this.updateToSchemaVersion(111); } + + if (databaseSchemaVersion < 112) { + await this.$executeQuery(this.getCreateFlagsValuesTableQuery(), await this.$checkIfTableExists('flag_values')); + await this.updateToSchemaVersion(112); + } } /** @@ -1841,6 +1846,18 @@ class DatabaseMigration { ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; } + private getCreateFlagsValuesTableQuery(): string { + return `CREATE TABLE IF NOT EXISTS flag_values ( + bucket_size enum('1', '1008', '4032') NOT NULL, + start_height int unsigned NOT NULL, + avg_timestamp timestamp NOT NULL, + flag_value bigint unsigned NOT NULL, + tx_count int unsigned NOT NULL, + vsize_total int unsigned NOT NULL, + PRIMARY KEY (bucket_size, start_height, flag_value) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8`; + } + /** @asyncUnsafe */ public async $blocksReindexingTruncate(): Promise { logger.warn(`Truncating pools, blocks, hashrates and difficulty_adjustments tables for re-indexing (using '--reindex-blocks'). You can cancel this command within 5 seconds`); diff --git a/backend/src/repositories/FlagValueRepository.ts b/backend/src/repositories/FlagValueRepository.ts new file mode 100644 index 000000000..66f8495c1 --- /dev/null +++ b/backend/src/repositories/FlagValueRepository.ts @@ -0,0 +1,133 @@ +import DB from '../database'; +import logger from '../logger'; + +export const INDEXING_PRESETS = [ + {name: 'per block', bucketSize: 1, retentionSpan: 144}, // block span of ~1 day + {name: 'per week', bucketSize: 1008, retentionSpan: -1}, // all + {name: 'per month', bucketSize: 4032, retentionSpan: -1}, // all +]; + +export const INTERVAL_PRESETS = { + '24h': {retentionSpan: 144, bucketSizes: [1]}, + '6m': {retentionSpan: 24192, bucketSizes: [1008, 4032]}, + '1y': {retentionSpan: 48384, bucketSizes: [1008, 4032]}, + '2y': {retentionSpan: 96768, bucketSizes: [1008, 4032]}, + '3y': {retentionSpan: 145152, bucketSizes: [1008, 4032]}, + 'all': {retentionSpan: -1, bucketSizes: [1008, 4032]}, +}; + +class FlagValuesRepository { + /** + * Get the latest indexed day from the database + * + * @asyncSafe */ + public async $getTipAndTailIndexedByBucketSize(bucketSize: number): Promise<{tip: number, tail: number} | null> { + try { + const [rows]: any[] = await DB.query(`SELECT (MAX(start_height) + ?) as tip, MIN(start_height) as tail FROM flag_values WHERE bucket_size = ?`, [bucketSize, bucketSize.toString()]); + if (rows !== null && rows.length > 0 && rows[0].tip !== null && rows[0].tail !== null) { + return rows[0]; + } + } catch (e) { + logger.err(`Cannot get tip and tail indexed from flag_values. Reason: ` + (e instanceof Error ? e.message : e)); + } + return null; + } + + /** + * Get the set of bucket that area already indexed between heights by bucketSize + * + * @asyncSafe */ + public async $getIndexedStartHeights(bucketSize: number, startHeight: number, latestHeight: number): Promise { + try { + const [rows]: any[] = await DB.query( + `SELECT DISTINCT start_height FROM flag_values WHERE bucket_size = ? AND start_height <= ? AND start_height >= ?`, + [bucketSize.toString(), startHeight, latestHeight] + ); + return rows.map(row => row.start_height); + } catch (e) { + logger.err(`Cannot get indexed start heights from flag_values. Reason: ` + (e instanceof Error ? e.message : e)); + } + return []; + } + + public async $saveBatchFlagValues(bucketSize: number, startHeight: number, dataPerFlag: Record>, avgTimestamp: number): Promise { + const params: any[] = []; + const distinctFlags = Object.keys(dataPerFlag); + const avgDate = new Date(Math.round(avgTimestamp) * 1000); + for (const flag of distinctFlags) { + params.push([bucketSize.toString(), startHeight, avgDate, BigInt(flag), dataPerFlag[flag].txCount, dataPerFlag[flag].vSizeTotal]); + } + try { + await DB.query(` + INSERT INTO flag_values (bucket_size, start_height, avg_timestamp, flag_value, tx_count, vsize_total) VALUES ? + ON DUPLICATE KEY UPDATE + avg_timestamp = VALUES(avg_timestamp), tx_count = VALUES(tx_count), vsize_total = VALUES(vsize_total) + `, [params]); + } catch (e) { + logger.debug(`Cannot save flag batched values. Reason: ${e instanceof Error ? e.message : e}`); + throw e; + } + } + + public async $queryTxCountBasedOnMask(mask: bigint, bucketSize: number, op: 'and' | 'or' | 'nor' | undefined, startHeight: number): Promise<{bucketSize: string, startHeight: number, avgTimestamp: number, txCount: number, vSizeTotal: number}[]> { + let flagPredicate = ''; + let params: any[]= []; + switch (op) { + case 'and': { + flagPredicate = 'AND (flag_value & ?) = ?'; + params = [bucketSize.toString(), startHeight, mask, mask]; + } break; + case 'or': { + flagPredicate = 'AND (flag_value & ?) > 0'; + params = [bucketSize.toString(), startHeight, mask]; + } break; + case 'nor': { + flagPredicate = 'AND (flag_value & ?) = 0'; + params = [bucketSize.toString(), startHeight, mask]; + } break; + case undefined: { // op not passed, no boolean operations + params = [bucketSize.toString(), startHeight]; + break; + } + default: throw new Error(`Invalid op '${op}', expected 'and' | 'or' | 'nor' | undefined`); + } + try { + const [rows]: any[] = await DB.query(` + SELECT bucket_size as bucketSize, start_height as startHeight, UNIX_TIMESTAMP(avg_timestamp) as avgTimestamp, + SUM(tx_count) as txCount, SUM(vsize_total) as vSizeTotal + FROM flag_values + WHERE bucket_size = ? AND start_height >= ? ${flagPredicate} + GROUP BY start_height ORDER BY start_height DESC + `, params); + if (rows !== null && rows.length > 0) { + return rows; + } + } catch (e) { + logger.debug(`Cannot get tx counts. Reason: ${e instanceof Error ? e.message : e}`); + } + return []; + } + + /** @asyncSafe */ + public async $deleteFlagValuesBelowHeight(height: number, bucketSize: number): Promise { + try { + await DB.query(`DELETE FROM flag_values WHERE start_height < ? AND bucket_size = ?`, [height, bucketSize.toString()]); + } catch(e) { + logger.err(`Cannot delete flag values below block #${height}. Reason: ` + (e instanceof Error ? e.message : e)); + } + } + + /** @asyncSafe */ + public async $deleteFlagValuesFromHeight(height: number): Promise { + try { + for (const preset of INDEXING_PRESETS) { + const startHeight = Math.floor(height / preset.bucketSize) * preset.bucketSize; + await DB.query(`DELETE FROM flag_values WHERE start_height >= ? AND bucket_size = ?`, [startHeight, preset.bucketSize.toString()]); + } + } catch (e) { + logger.err(`Cannot delete flag values above ${height}. Reason: ` + (e instanceof Error ? e.message : e)); + } + } +} + +export default new FlagValuesRepository(); From 25ae5109b917d1d5f74fa3d5b728feb0b9254c55 Mon Sep 17 00:00:00 2001 From: rodribp Date: Mon, 10 Aug 2026 01:45:04 -0600 Subject: [PATCH 363/369] add: backend indexing + reorg handling --- backend/src/api/blocks.ts | 155 +++++++++++++++++- backend/src/indexer.ts | 2 + .../repositories/BlocksSummariesRepository.ts | 28 ++++ 3 files changed, 184 insertions(+), 1 deletion(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index be247f571..97276a922 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -40,6 +40,7 @@ import CpfpRepository from '../repositories/CpfpRepository'; import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script'; import database from '../database'; import { getBlockFirstSeenFromLogs, getOldestLogTimestampFromLogs, scanLogsForBlocksFirstSeen } from '../utils/file-read'; +import FlagValueRepository, { INDEXING_PRESETS } from '../repositories/FlagValueRepository'; class Blocks { private blocks: BlockExtended[] = []; @@ -54,6 +55,8 @@ class Blocks { private oldestCoreLogTimestamp: number | undefined | null = undefined; private mainLoopTimeout: number = 120000; + private indexingFlagValues: boolean = false; + private flagValuesDeleteQueue: number[]= []; constructor() { } @@ -689,6 +692,155 @@ class Blocks { } } + /** + * [INDEXING] Index all blocks flag values for the goggles graph rendering + * + * @asyncSafe + */ + public async $generateFlagValuesDatabase(): Promise { + const MAX_BLOCKS_PERQUERY = 144; + if (this.indexingFlagValues) { + return; + } + + if (Common.blocksSummariesIndexingEnabled() === false || Common.isLiquid()) { + return; + } + + this.indexingFlagValues = true; + + const tipOfSummaries = await BlocksSummariesRepository.$getTipIndexed(); + if (!tipOfSummaries) { + this.indexingFlagValues = false; + return; + } + + let newlyIndexedBuckets = 0; + + while (this.flagValuesDeleteQueue.length > 0) { // Deletion of in-queue heights due to reorg + const deletionHeight = this.flagValuesDeleteQueue.shift(); + if (deletionHeight === undefined) { + continue; + } + await FlagValueRepository.$deleteFlagValuesFromHeight(deletionHeight); + } + + for (const preset of INDEXING_PRESETS) { + let seedHeight = preset.retentionSpan > -1 ? tipOfSummaries - preset.retentionSpan : 0; + if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT > 0) { + seedHeight = Math.max(seedHeight, tipOfSummaries - config.MEMPOOL.INDEXING_BLOCKS_AMOUNT + 1); + } + const firstBucket = Math.floor((tipOfSummaries + 1) / preset.bucketSize) * preset.bucketSize - preset.bucketSize; + const lastBucket = Math.max(0, Math.floor(seedHeight / preset.bucketSize) * preset.bucketSize); + + // Deletion of flag values out of retention span + const tipAndTailOfFlagValues = await FlagValueRepository.$getTipAndTailIndexedByBucketSize(preset.bucketSize); + if (tipAndTailOfFlagValues && lastBucket > tipAndTailOfFlagValues.tail) { // Drop buckets that fell out of block span + logger.debug(`Deleting all the flag values ${preset.name} below height #${lastBucket}`, logger.tags.goggles); + await FlagValueRepository.$deleteFlagValuesBelowHeight(lastBucket, preset.bucketSize); + } + + if (firstBucket < lastBucket) { + continue; // no complete bucket in range + } + + const indexedBuckets = await FlagValueRepository.$getIndexedStartHeights(preset.bucketSize, firstBucket, lastBucket); + const isBucketIndexed = {}; + // We map the buckets that are already indexed to skip them + for (const startHeight of indexedBuckets) { + isBucketIndexed[startHeight] = true; + } + + logger.debug(`Processing and indexing flag values from #${firstBucket} to #${lastBucket} ${preset.name}`, logger.tags.goggles); + + let timer = Date.now() / 1000; + const startedAt = Date.now() / 1000; + let blocksComputedInTotal = 0; + let blocksComputedThisRun = 0; + const blocksToCompute = firstBucket + preset.bucketSize - lastBucket - (indexedBuckets.length * preset.bucketSize); + for (let bucketStart = firstBucket; bucketStart >= lastBucket; bucketStart -= preset.bucketSize) { + if (isBucketIndexed[bucketStart]) { + continue; // already indexed + } + try { + const bucketFirstHeight = bucketStart + preset.bucketSize - 1; + const bucketLastHeight = bucketStart - 1; + + let step = bucketFirstHeight; + + const dataPerFlag: Record> = {}; + let sumTimestamps = 0; + let nBlocks = 0; + let incomplete = false; + + // Incrementalized logic capped by max blocks per query, not bucket size + while (step > bucketLastHeight) { + const blocksPerQuery = Math.min(step - bucketLastHeight, MAX_BLOCKS_PERQUERY); + const cappedLastHeight = step - blocksPerQuery; + + const blocks = await BlocksSummariesRepository.$getSummariesBetweenHeights(step, cappedLastHeight); + await Common.sleep$(250); // Don't query/index flag values too fast + + if (!blocks || blocks.length < blocksPerQuery) { + incomplete = true; + break; // Incomplete bucket + } + + // Flag values processing + for (const block of blocks) { + const txData = JSON.parse(block.transactions).map((tx) => ({flags: tx.flags, vsize: tx.vsize})); + for (const data of txData) { + if (dataPerFlag[data.flags] === undefined || Object.keys(dataPerFlag[data.flags]).length === 0) { + dataPerFlag[data.flags] = { + txCount: 0, + vSizeTotal: 0 + }; + } + dataPerFlag[data.flags].txCount = dataPerFlag[data.flags].txCount + 1; + dataPerFlag[data.flags].vSizeTotal = dataPerFlag[data.flags].vSizeTotal + data.vsize; + } + sumTimestamps += block.timestamp; + blocksComputedInTotal++; + blocksComputedThisRun++; + nBlocks++; + } + + // Logging + const elapsedSeconds = (Date.now() / 1000) - timer; + if (elapsedSeconds > 5) { + const runningFor = (Date.now() / 1000) - startedAt; + const blocksPerSecond = blocksComputedThisRun / elapsedSeconds; + const completion = (blocksComputedInTotal / blocksToCompute) * 100; + logger.debug(`Indexing flag values ${preset.name} | ${blocksComputedInTotal}/${blocksToCompute} (${completion.toFixed(2)}%) | ~${blocksPerSecond.toFixed(2)} blocks/sec | elapsed: ${runningFor.toFixed(2)} seconds`,logger.tags.goggles); + timer = Date.now() / 1000; + blocksComputedThisRun = 0; + } + + step -= blocksPerQuery; + } + + if (incomplete) { + continue; + } + + const avgTimestamp = sumTimestamps / nBlocks; + await FlagValueRepository.$saveBatchFlagValues(preset.bucketSize, bucketStart, dataPerFlag, avgTimestamp); + nBlocks = 0; + newlyIndexedBuckets++; + } catch (e) { + logger.err(`Failed to index flag values between #${bucketStart} and #${bucketStart + preset.bucketSize - 1}. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.goggles); + } + } + logger.debug(`Successfully indexed #${blocksComputedInTotal} blocks ${preset.name} in ${((Date.now() / 1000) - startedAt).toFixed(2)} seconds`, logger.tags.goggles); + } + if (newlyIndexedBuckets > 0) { + logger.notice(`Flag values indexing completed: indexed ${newlyIndexedBuckets} buckets`, logger.tags.goggles); + } else { + logger.debug(`Flag values indexing completed: indexed ${newlyIndexedBuckets} buckets`, logger.tags.goggles); + } + this.indexingFlagValues = false; + } + /** @asyncUnsafe */ public async $indexBlockSummary(hash: string, height: number, stale?: boolean): Promise { if (config.MEMPOOL.BACKEND === 'esplora') { @@ -1409,6 +1561,7 @@ class Blocks { await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(forkTail.height); await cpfpRepository.$deleteClustersFrom(forkTail.height); await AccelerationRepository.$deleteAccelerationsFrom(forkTail.height); + this.flagValuesDeleteQueue.push(forkTail.height); chainTips.clearOrphanCacheAboveHeight(forkTail.height); this.updateTimerProgress(timer, `deleted stale block data`); @@ -1769,7 +1922,7 @@ class Blocks { if (transactions?.length != null) { const { cpfpSummary } = await detectTemplateAlgorithm(height, transactions, [], true); - if (!stale) { + if (!stale && Common.cpfpIndexingEnabled() === true) { await this.$saveCpfp(hash, height, cpfpSummary); } diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts index ff10d701e..9d47de2f2 100644 --- a/backend/src/indexer.ts +++ b/backend/src/indexer.ts @@ -226,6 +226,8 @@ class Indexer { await AccelerationRepository.$indexPastAccelerations(); await BlocksAuditsRepository.$migrateAuditsV0toV1(); await BlocksRepository.$migrateBlocks(); + + void blocks.$generateFlagValuesDatabase(); // do not wait for classify blocks to finish void blocks.$classifyBlocks(); runSuccessful = true; diff --git a/backend/src/repositories/BlocksSummariesRepository.ts b/backend/src/repositories/BlocksSummariesRepository.ts index fc2771ce8..2239fa258 100644 --- a/backend/src/repositories/BlocksSummariesRepository.ts +++ b/backend/src/repositories/BlocksSummariesRepository.ts @@ -216,6 +216,34 @@ class BlocksSummariesRepository { } return false; } + + /** @asyncSafe */ + public async $getTipIndexed(): Promise { + if (!Common.blocksSummariesIndexingEnabled()) { + return null; + } + try { + const [row]: any[] = await DB.query('SELECT MAX(height) as tip FROM blocks_summaries WHERE version >= 1'); + + if (row !== null && row.length > 0) { + return row[0].tip; + } + } catch (e) { + logger.err(`Cannot get latest block summary. Reason: ` + (e instanceof Error ? e.message : e)); + } + return null; + } + + public async $getSummariesBetweenHeights(startHeight: number, latestHeight: number): Promise<{height: number, transactions: string, timestamp: number}[]> { + try { + const [rows]: any[] = await DB.query(`SELECT bs.height, bs.transactions, UNIX_TIMESTAMP(b.blockTimestamp) as timestamp FROM blocks_summaries bs JOIN blocks b ON bs.id = b.hash WHERE bs.height <= ? AND bs.height > ? AND b.stale = 0 AND bs.version >= 1 ORDER BY height DESC`, [startHeight, latestHeight]); + + return rows; + } catch (e) { + logger.err(`Cannot get blocks between ${startHeight} and ${latestHeight}. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } } export default new BlocksSummariesRepository(); From 1af9dfd4571981b8904fcd5420dae2e3a42119ea Mon Sep 17 00:00:00 2001 From: rodribp Date: Mon, 10 Aug 2026 01:45:29 -0600 Subject: [PATCH 364/369] add: flag values endpoints --- backend/src/api/bitcoin/bitcoin.routes.ts | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index e10157b1a..73741efbf 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -23,12 +23,14 @@ import { calculateMempoolTxCpfp } from '../cpfp'; import { handleError } from '../../utils/api'; import poolsUpdater from '../../tasks/pools-updater'; import chainTips from '../chain-tips'; +import FlagValueRepository, { INTERVAL_PRESETS } from '../../repositories/FlagValueRepository'; const TXID_REGEX = /^[a-f0-9]{64}$/i; const BLOCK_HASH_REGEX = /^[a-f0-9]{64}$/i; const ADDRESS_REGEX = /^[a-z0-9]{2,120}$/i; const SCRIPT_HASH_REGEX = /^([a-f0-9]{2})+$/i; const MAX_TRANSACTION_TIMES = 100; +const JUST_NUMBERS_REGEX = /^[1-9]\d*$/; class BitcoinRoutes { public initRoutes(app: Application) { @@ -70,6 +72,10 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/definition/list', this.getBlockDefinitionHashes) .get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/definition/current', this.getCurrentBlockDefinitionHash) .get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/:definitionHash', this.getBlocksByDefinitionHash) + + .get(config.MEMPOOL.API_URL_PREFIX + 'goggles/:interval/', this.getTxCountPerFlagValue) + .get(config.MEMPOOL.API_URL_PREFIX + 'goggles/:interval/:bucketSize', this.getTxCountPerFlagValue) + .get(config.MEMPOOL.API_URL_PREFIX + 'goggles/:interval/:bucketSize/:op/:mask', this.getTxCountPerFlagValue) ; if (config.MEMPOOL.BACKEND !== 'esplora') { @@ -1132,6 +1138,61 @@ class BitcoinRoutes { } } + private async getTxCountPerFlagValue(req: Request, res: Response) { + try { + if (!Common.blocksSummariesIndexingEnabled()) { + handleError(req, res, 404, `Block summaries indexing is required for this API`); + return; + } + + const presets = INTERVAL_PRESETS; + const operations = ['and', 'or', 'nor', undefined]; + const intervals = Object.keys(presets); + const interval = req.params.interval; + + if (!intervals.includes(interval)) { + handleError(req, res, 400, `Invalid interval, must be one of ${intervals.toString()}`); + return; + } + + const validBucketSizes = presets[interval].bucketSizes; + const rawBucketSize = req.params.bucketSize; + const bucketSize: number = rawBucketSize === undefined ? validBucketSizes[0] : Number(rawBucketSize); + if (!Number.isInteger(bucketSize) || !validBucketSizes.includes(bucketSize)) { + handleError(req, res, 400, `Invalid bucket size, must be ${validBucketSizes.toString()}`); + return; + } + + if (!operations.includes(req.params.op)) { + handleError(req, res, 400, `Invalid operation, must be 'and', 'or', 'nor' or undefined.`); + return; + } + + if (req.params.mask && !JUST_NUMBERS_REGEX.test(req.params.mask)) { + handleError(req, res, 400, `Invalid mask value, must be a positive integer`); + return; + } + + const op = (req.params.op) as 'and' | 'or' | 'nor' | undefined; + const mask = BigInt(req.params.mask ?? 0n); + + const { tip } = await FlagValueRepository.$getTipAndTailIndexedByBucketSize(bucketSize) || { tip: undefined }; + + if (!tip) { + handleError(req, res, 400, `Failed to get latest indexed flag values for ${interval}`); + return; + } + + const startHeight = presets[interval].retentionSpan !== -1 ? (tip - presets[interval].retentionSpan) : -1; + const txsCount = await FlagValueRepository.$queryTxCountBasedOnMask(mask, bucketSize, op, startHeight); + res.header('X-total-count', tip.toString()); + res.header('Expires', new Date(Date.now() + 1000 * 3600 * 24 * (presets[interval].bucketSizes[0] / 144)).toUTCString()); + res.send(txsCount); + } catch (e: any) { + handleError(req, res, 400, e instanceof Error ? e.message : 'Failed to get flag values'); + } + } + private async $postTransaction(req: Request, res: Response) { res.setHeader('content-type', 'text/plain'); try { From abd8defcbb794e29851e19a33fead0175f8d4a75 Mon Sep 17 00:00:00 2001 From: rodribp Date: Mon, 10 Aug 2026 01:46:21 -0600 Subject: [PATCH 365/369] add: mempool goggles graph to the frontend --- .../block-filters.component.html | 2 +- .../block-filters/block-filters.component.ts | 1 + .../block-goggles-graph.component.html | 77 ++ .../block-goggles-graph.component.scss | 195 +++++ .../block-goggles-graph.component.ts | 667 ++++++++++++++++++ .../components/graphs/graphs.component.html | 3 + frontend/src/app/graphs/graphs.module.ts | 2 + .../src/app/graphs/graphs.routing.module.ts | 6 + frontend/src/app/services/api.service.ts | 7 + 9 files changed, 959 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.html create mode 100644 frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.scss create mode 100644 frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.ts 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 33dfb621d..7c8585fb3 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -25,7 +25,7 @@ -
    +
    Tint
    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 d9e8691d2..2dc78c782 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.ts +++ b/frontend/src/app/components/block-filters/block-filters.component.ts @@ -13,6 +13,7 @@ import { Subscription } from 'rxjs'; export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy { @Input() cssWidth: number = 800; @Input() excludeFilters: string[] = []; + @Input() showTint: boolean = true; @Output() onFilterChanged: EventEmitter = new EventEmitter(); filterSubscription: Subscription; diff --git a/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.html b/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.html new file mode 100644 index 000000000..2b94caf31 --- /dev/null +++ b/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.html @@ -0,0 +1,77 @@ + + +
    +
    +
    + Mempool goggles + +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + +
    \ No newline at end of file diff --git a/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.scss b/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.scss new file mode 100644 index 000000000..4dd2d3c3d --- /dev/null +++ b/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.scss @@ -0,0 +1,195 @@ +.card-header { + border-bottom: 0; + font-size: 18px; + @media (min-width: 465px) { + font-size: 20px; + } + @media (min-width: 1340px) { + height: 40px; + } +} + +.graph-toolbar { + @media (min-width: 1340px) { + display: contents; + } + @media (max-width: 1339px) { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 6px; + } +} + +.full-container .card-header .graph-toolbar .formRadioGroup { + @media (max-width: 1339px) { + position: static; + float: none; + flex-direction: row; + margin: 0; + } +} + +.full-container .card-header .graph-toolbar .interval-group { + @media (max-width: 829px) { + width: 100%; + .btn-group { + display: flex; + width: 100%; + .btn { + flex: 1; + } + } + } +} + +.main-title { + position: relative; + color: var(--fg); + opacity: var(--opacity); + margin-top: -13px; + font-size: 10px; + text-transform: uppercase; + font-weight: 500; + text-align: center; + padding-bottom: 3px; +} + +.full-container { + display: flex; + flex-direction: column; + padding: 0px 15px; + width: 100%; + height: calc(100vh - 225px); + min-height: 400px; + @media (min-width: 992px) { + height: calc(100vh - 150px); + } +} + +.chart { + display: flex; + flex: 1; + height: 100%; + padding-bottom: 20px; + padding-right: 10px; + @media (max-width: 992px) { + padding-bottom: 25px; + } + @media (max-width: 829px) { + padding-bottom: 50px; + } + @media (max-width: 767px) { + padding-bottom: 25px; + } + @media (max-width: 629px) { + padding-bottom: 55px; + } + @media (max-width: 567px) { + padding-bottom: 55px; + } +} + +.goggles-chart-wrapper { + position: relative; + + .echarts-container { + flex: 1; + width: 100%; + height: 100%; + } + + // the goggles toggle is hidden by default (reveals on canvas hover); surface it for the graph + ::ng-deep .block-filters .menu-toggle { + opacity: 0.5; + } + &:hover ::ng-deep .block-filters .menu-toggle { + opacity: 1; + } +} +.chart-widget { + width: 100%; + height: 100%; + max-height: 238px; +} + +.block-fee-rates { + min-height: 56px; + display: block; + @media (min-width: 485px) { + display: flex; + flex-direction: row; + } + h5 { + margin-bottom: 10px; + } + .item { + width: 50%; + display: inline-block; + margin: 0px auto 20px; + &:nth-child(2) { + order: 2; + @media (min-width: 485px) { + order: 3; + } + } + &:nth-child(3) { + order: 3; + @media (min-width: 485px) { + order: 2; + display: block; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: block; + } + } + .card-title { + font-size: 1rem; + color: var(--title-fg); + } + .card-text { + font-size: 18px; + span { + color: var(--transparent-fg); + font-size: 12px; + } + } + } +} + +.formRadioGroup { + margin-top: 6px; + display: flex; + flex-direction: column; + @media (min-width: 991px) { + position: relative; + top: -100px; + } + @media (min-width: 830px) and (max-width: 991px) { + position: relative; + top: 0px; + } + @media (min-width: 830px) { + flex-direction: row; + float: right; + margin-top: 0px; + margin-left: 2px; + margin-right: 2px; + } + .btn-sm { + font-size: 9px; + @media (min-width: 830px) { + font-size: 14px; + } + } +} + +.skeleton-loader { + width: 100%; + display: block; + max-width: 80px; + margin: 15px auto 3px; +} diff --git a/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.ts b/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.ts new file mode 100644 index 000000000..08a276d8a --- /dev/null +++ b/frontend/src/app/components/block-goggles-graph/block-goggles-graph.component.ts @@ -0,0 +1,667 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core'; +import { EChartsOption } from '@app/graphs/echarts'; +import { BehaviorSubject, combineLatest, forkJoin, Observable, of } from 'rxjs'; +import { catchError, debounceTime, distinctUntilChanged, filter, map, share, startWith, switchMap, tap } from 'rxjs/operators'; +import { ActiveFilter, FilterMode, toFilters, toFlags } from '@app/shared/filters.utils'; +import { ApiService } from '@app/services/api.service'; +import { formatNumber } from '@angular/common'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '@app/shared/graphs.utils'; +import { StorageService } from '@app/services/storage.service'; +import { MiningService } from '@app/services/mining.service'; +import { selectPowerOfTen } from '@app/bitcoin.utils'; +import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe'; +import { StateService } from '@app/services/state.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { HttpResponse } from '@angular/common/http'; +import { VbytesPipe } from '@app/shared/pipes/bytes-pipe/vbytes.pipe'; +import { SeoService } from '@app/services/seo.service'; + +interface GogglesRollup { + bucketSize: string; + startHeight: number; + avgTimestamp: number; + txCount: number; + vSizeTotal: number; +} + +interface GogglesDatum { + value: number; + startHeight: number; + bucketSize: number; + txCount: number; + vSizeTotal: number; + timestampMs: number; + baseTxCount?: number; + baseVSize?: number; +} + +const INTERVAL_PRESETS: Record = { + '24h': [1], + '6m': [1008, 4032], + '1y': [1008, 4032], + '2y': [1008, 4032], + '3y': [1008, 4032], + 'all': [1008, 4032], +}; + +@Component({ + selector: 'app-block-goggles-graph', + templateUrl: './block-goggles-graph.component.html', + styleUrls: ['./block-goggles-graph.component.scss'], + styles: [` + .loadingGraphs { + position: absolute; + top: 50%; + left: calc(50% - 15px); + z-index: 99; + } + `], + standalone: false, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BlockGogglesGraphComponent implements OnInit { + @Input() widget = false; + @Input() right: number | string = 45; + @Input() left: number | string = 75; + + miningWindowPreference: string; + radioGroupForm: UntypedFormGroup; + unitGroupForm: UntypedFormGroup; + bucketGroupForm: UntypedFormGroup; + modeGroupForm: UntypedFormGroup; + count = $localize`:@@8177873832400820695:Count`; + allLabel = $localize`All transactions`; + transactionsLabel = $localize`Transactions`; + matchedLabel = $localize`Matched`; + + chartOptions: EChartsOption = {}; + chartInitOptions = { + renderer: 'svg', + }; + + statsObservable$: Observable; + isLoading = true; + formatNumber = formatNumber; + timespan = ''; + chartInstance: any = undefined; + + // active goggles filter; empty op/mask means no filter, so the backend returns total tx counts + goggle$ = new BehaviorSubject<{ op?: FilterMode, mask?: bigint }>({}); + + private intervals = Object.keys(INTERVAL_PRESETS); + + private bucketTimestampByHeight = new Map(); + + private totalsCache: Record = {}; + + private relativeMode = false; + + private prefs: { unit: string, bucket: number, mode: string } = { unit: 'txCount', bucket: 1008, mode: 'abs' }; + + constructor( + @Inject(LOCALE_ID) public locale: string, + private apiService: ApiService, + private formBuilder: UntypedFormBuilder, + private storageService: StorageService, + private miningService: MiningService, + public stateService: StateService, + private router: Router, + private zone: NgZone, + private route: ActivatedRoute, + private cd: ChangeDetectorRef, + private seoService: SeoService, + private vbytesPipe: VbytesPipe, + ) { + this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' }); + this.radioGroupForm.controls.dateSpan.setValue('1y'); + this.unitGroupForm = this.formBuilder.group({ unitType: 'txCount'}); + this.unitGroupForm.controls.unitType.setValue('txCount'); + this.bucketGroupForm = this.formBuilder.group({ bucketSize: 1008}); + this.bucketGroupForm.controls.bucketSize.setValue(1008); + this.modeGroupForm = this.formBuilder.group({ mode: 'abs' }); + this.modeGroupForm.controls.mode.setValue('abs'); + } + + ngOnInit(): void { + let firstRun = true; + if (this.widget) { + this.miningWindowPreference = '6m'; + } else { + this.seoService.setTitle($localize`Mempool Goggles`); + this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.goggles:See Bitcoin transactions matching Mempool Goggles filters visualized over time.`); + this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); + } + if (!this.intervals.includes(this.miningWindowPreference)) { + this.miningWindowPreference = '1y'; + } + + this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); + this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); + let storedPrefs: any = {}; + try { + storedPrefs = JSON.parse(this.storageService.getValue('goggles_prefs')) ?? {}; + } catch { + storedPrefs = {}; + } + if (['vb', 'txCount'].includes(storedPrefs.unit)) { + this.prefs.unit = storedPrefs.unit; + } + if ([1008, 4032].includes(storedPrefs.bucket)) { + this.prefs.bucket = storedPrefs.bucket; + } + if (['abs', 'rel'].includes(storedPrefs.mode)) { + this.prefs.mode = storedPrefs.mode; + } + this.unitGroupForm = this.formBuilder.group({ unitType: this.prefs.unit }); + this.unitGroupForm.controls.unitType.setValue(this.prefs.unit); + this.bucketGroupForm.controls.bucketSize.setValue(this.prefs.bucket, { emitEvent: false }); + this.modeGroupForm.controls.mode.setValue(this.prefs.mode, { emitEvent: false }); + + if (!this.widget) { + this.route + .fragment + .subscribe((fragment) => { + this.parseFragment(fragment); + }); + } + + this.statsObservable$ = combineLatest([ + this.radioGroupForm.get('dateSpan').valueChanges.pipe( + startWith(this.radioGroupForm.controls.dateSpan.value), + distinctUntilChanged(), + ), + // debounce so toggling several flags fires one request; startWith keeps the first paint immediate + this.goggle$.pipe( + debounceTime(250), + startWith(this.goggle$.value), + distinctUntilChanged((a, b) => a.op === b.op && a.mask === b.mask), + ), + this.unitGroupForm.get('unitType').valueChanges.pipe( + startWith(this.unitGroupForm.controls.unitType.value), + distinctUntilChanged(), + ), + this.bucketGroupForm.get('bucketSize').valueChanges.pipe( + startWith(this.bucketGroupForm.controls.bucketSize.value), + distinctUntilChanged(), + ), + this.modeGroupForm.get('mode').valueChanges.pipe( + startWith(this.modeGroupForm.controls.mode.value), + distinctUntilChanged(), + ), + ]).pipe( + switchMap(([timespan, goggle, unitType, bucketSize, mode]) => { + if (!this.widget && !firstRun && timespan !== this.timespan) { + this.storageService.setValue('miningWindowPreference', timespan); + } + firstRun = false; + this.timespan = timespan; + this.isLoading = true; + // reconcile the bucket size with the interval (e.g. 24h is per-block only) and keep the UI radio in sync + const allowedBuckets = this.bucketSizesForInterval(timespan); + const effectiveBucket = allowedBuckets.includes(bucketSize) ? bucketSize : allowedBuckets[0]; + if (effectiveBucket !== this.bucketGroupForm.controls.bucketSize.value) { + this.bucketGroupForm.controls.bucketSize.setValue(effectiveBucket, { emitEvent: false }); + } + const effectiveMode = goggle.mask ? mode : 'abs'; + if (effectiveMode !== this.modeGroupForm.controls.mode.value) { + this.modeGroupForm.controls.mode.setValue(effectiveMode, { emitEvent: false }); + } + this.prefs.unit = unitType; + if (allowedBuckets.length > 1) { + this.prefs.bucket = effectiveBucket; + } + if (goggle.mask) { + this.prefs.mode = effectiveMode; + } + this.storageService.setValue('goggles_prefs', JSON.stringify(this.prefs)); + const cacheKey = `${timespan}:${effectiveBucket}`; + const filtered$ = this.apiService.getHistoricalTxCountByFlags$(timespan, effectiveBucket.toString(), goggle.op, goggle.mask?.toString()); + const totals$ = goggle.mask + ? (this.totalsCache[cacheKey] + ? of(this.totalsCache[cacheKey]) + : this.apiService.getHistoricalTxCountByFlags$(timespan, effectiveBucket.toString()).pipe( + map((res) => res.body || []), + tap((body) => { this.totalsCache[cacheKey] = body; }), + )) + : of(null); + const unit = of(unitType); + return forkJoin<[HttpResponse, GogglesRollup[], string]>([filtered$, totals$, unit]).pipe( + tap(([response, totalsBody, unit]) => { + const body: GogglesRollup[] = response.body || []; + const filtered = !!this.goggle$.value.mask; + // when filtering, body is the matched rows and totalsBody the unfiltered totals; otherwise body itself is the totals + const totalRows: GogglesRollup[] = filtered ? (totalsBody || []) : body; + const matchedRows: GogglesRollup[] = filtered ? body : []; + const unitIsTx = unit === 'txCount'; + this.relativeMode = filtered && effectiveMode === 'rel'; + const matchedByHeight = new Map(); + for (const row of matchedRows) { + matchedByHeight.set(row.startHeight, row); + } + const sorted = [...totalRows].sort((a, b) => a.startHeight - b.startHeight); + const categories = sorted.map((row) => row.startHeight); + this.bucketTimestampByHeight = new Map(sorted.map((row) => [row.startHeight, Number(row.avgTimestamp) * 1000])); + const toSeries = (matched = false): GogglesDatum[] => sorted.map((row) => { + const bucketSize = parseInt(row.bucketSize, 10) || 1; + const source = matched ? matchedByHeight.get(row.startHeight) : row; + const txCount = source ? Number(source.txCount) : 0; + const vSizeTotal = source ? Number(source.vSizeTotal) : 0; + const selected = unitIsTx ? txCount : vSizeTotal; + const plotted = bucketSize > 1 ? selected / bucketSize : selected; + const datum: GogglesDatum = { value: plotted, startHeight: row.startHeight, bucketSize, txCount, vSizeTotal, timestampMs: Number(row.avgTimestamp) * 1000 }; + if (matched) { + datum.baseTxCount = Number(row.txCount); + datum.baseVSize = Number(row.vSizeTotal); + if (this.relativeMode) { + const base = unitIsTx ? datum.baseTxCount : datum.baseVSize; + datum.value = base > 0 ? selected / base * 100 : 0; + } + } else if (this.relativeMode) { + datum.value = 100; + } + return datum; + }); + this.prepareChartOptions(categories, toSeries(), filtered ? toSeries(true) : []); + this.isLoading = false; + this.cd.markForCheck(); + }), + map(([response]) => { + const body: GogglesRollup[] = response.body || []; + const headerCount = parseInt(response.headers.get('x-total-count'), 10); + return { + blockCount: Number.isFinite(headerCount) ? headerCount : Number.MAX_SAFE_INTEGER, + txCount: body.reduce((acc, row) => acc + row.txCount, 0), + }; + }), + catchError(err => { + this.prepareChartOptions([], [], [], err); + this.isLoading = false; + this.cd.markForCheck(); + return of({ blockCount: Number.MAX_SAFE_INTEGER, txCount: 0 }); + }), + ); + }), + share(), + ); + } + + onFilterChanged(activeFilter: ActiveFilter | null): void { + const mask = activeFilter ? toFlags(activeFilter.filters) : 0n; + this.goggle$.next(mask > 0n + ? { op: activeFilter.mode, mask } + : {} + ); + if (!this.widget) { + this.router.navigate([], { relativeTo: this.route, fragment: this.getFragment(), replaceUrl: true }); + } + } + + // builds the URL fragment: just the interval when no filter is active ("1y"), or "interval=1y&op=and&mask=5" when filtering + getFragment(interval?: string): string { + const timespan = interval ?? this.radioGroupForm.controls.dateSpan.value; + const { op, mask } = this.goggle$.value; + return mask ? `interval=${timespan}&op=${op}&mask=${mask.toString()}` : timespan; + } + + // restores state from a fragment in either form, letting block-filters pick up restored filters via activeGoggles$ + private parseFragment(fragment: string): void { + if (!fragment) { + return; + } + if (this.intervals.includes(fragment)) { + this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); + return; + } + const params = new URLSearchParams(fragment); + const rawInterval = params.get('interval') ?? ''; + const interval = this.intervals.includes(rawInterval) ? rawInterval : this.radioGroupForm.controls.dateSpan.value; + const maskParam = params.get('mask') ?? ''; + const mask = maskParam && /^\d+$/.test(maskParam) ? BigInt(maskParam) : 0n; + const op = (['and', 'or', 'nor'].includes(params.get('op')) ? params.get('op') : 'and') as FilterMode; + + this.radioGroupForm.controls.dateSpan.setValue(interval, { emitEvent: false }); + // skip if already applied, otherwise the navigation in onFilterChanged would loop back here + if ((mask > 0n && (this.goggle$.value.mask ?? 0n) !== mask) || this.goggle$.value.op !== op) { + this.stateService.activeGoggles$.next({ mode: op, filters: toFilters(mask).map(f => f.key), gradient: 'fee' }); + } + } + + prepareChartOptions(categories: number[], totalData: GogglesDatum[], matchedData: GogglesDatum[], error?): void { + const filtered = !!this.goggle$.value.mask; + const perBlock = totalData.length > 0 && totalData[0].bucketSize === 1; + let title: object; + if (totalData.length === 0 ) { + title = { + textStyle: { + color: 'grey', + fontSize: 15 + }, + text: $localize`:@@23555386d8af1ff73f297e89dd4af3f4689fb9dd:Indexing blocks`, + left: 'center', + top: 'center' + }; + } + if (error && error.status === 404) { + title = { + textStyle: { + color: 'grey', + fontSize: 15 + }, + text: $localize`Block summaries indexing is required for this graph`, + left: 'center', + top: 'center' + }; + } + + const unitIsVb = this.unitGroupForm.controls.unitType.value === 'vb'; + const yAxisName = this.relativeMode + ? (unitIsVb ? $localize`Share of vsize (%)` : $localize`Share of txs (%)`) + : (unitIsVb ? $localize`Total vsize (vB)` : $localize`Total txs (Count)`); + this.chartOptions = { + title, + color: ['#1E88E5'], + animation: false, + grid: { + right: this.right, + left: this.left, + bottom: this.widget ? 30 : 80, + top: this.widget ? 20 : (this.isMobile() ? 10 : 50), + }, + tooltip: { + show: !this.isMobile(), + trigger: 'axis', + axisPointer: { + type: 'line' + }, + backgroundColor: 'rgba(17, 19, 31, 1)', + borderRadius: 4, + shadowColor: 'rgba(0, 0, 0, 0.5)', + textStyle: { + color: 'var(--tooltip-grey)', + align: 'left', + }, + borderColor: '#000', + formatter: function(params): string { + if (!params || params.length <= 0) { + return ''; + } + const baseline = params.find(p => p.seriesId === 'total'); + const matched = params.find(p => p.seriesId === 'matched'); + const anchor = baseline || matched; + if (!anchor) { + return ''; + } + const startHeight = anchor.data.startHeight; + const bucketSize = anchor.data.bucketSize || 1; + const timestampMs = anchor.data.timestampMs; + const baseTxCount = baseline ? baseline.data.txCount : (matched ? matched.data.baseTxCount : 0); + const baseVSize = baseline ? baseline.data.vSizeTotal : (matched ? matched.data.baseVSize : 0); + const filtered = !!this.goggle$.value.mask; + const unitIsTxCount = this.unitGroupForm.controls.unitType.value === 'txCount'; + const rolledUp = bucketSize > 1; + + const fmtCount = (v): string => formatNumber(v, this.locale, '1.0-0'); + const fmtAvg = (v): string => formatNumber(v, this.locale, '1.0-2'); + const fmtVSize = (v): string => this.vbytesPipe.transform(v, 2, 'vB', undefined, true); + const fmtPct = (v): string => formatNumber(v, this.locale, '1.0-2') + '%'; + + let tooltip = ''; + tooltip += `${formatterXAxis(this.locale, this.timespan, timestampMs)}
    `; + + const fmtVal = (v): string => unitIsTxCount + ? (rolledUp ? fmtAvg(v / bucketSize) : fmtCount(v)) + : fmtVSize(rolledUp ? v / bucketSize : v); + + if (baseline) { + tooltip += `${baseline.marker} ${baseline.seriesName}: ${fmtVal(unitIsTxCount ? baseTxCount : baseVSize)}
    `; + } + + if (filtered && matched) { + const matchedVal = unitIsTxCount ? matched.data.txCount : matched.data.vSizeTotal; + const base = unitIsTxCount ? baseTxCount : baseVSize; + tooltip += `${matched.marker} ${matched.seriesName}: ${fmtVal(matchedVal)}
    `; + if (base > 0) { + tooltip += `${matched.marker} ` + $localize`Share` + `: ${fmtPct(matchedVal / base * 100)}
    `; + } + } + + if (rolledUp) { + tooltip += `` + $localize`*On average between blocks ${startHeight} - ${startHeight + bucketSize - 1}` + ``; + } else { + tooltip += `` + $localize`At block: ${startHeight}` + ``; + } + return tooltip; + }.bind(this) + }, + xAxis: totalData.length === 0 ? undefined : { + name: this.widget ? undefined : formatterXAxisLabel(this.locale, this.timespan), + nameLocation: 'middle', + nameTextStyle: { + padding: [10, 0, 0, 0], + }, + type: 'category', + data: categories, + axisLine: { onZero: false }, + splitLine: { show: false }, + axisLabel: { + formatter: (value): string => { + const ts = this.bucketTimestampByHeight.get(Number(value)); + return ts !== undefined ? formatterXAxisTimeCategory(this.locale, this.timespan, ts) : ''; + }, + align: 'center', + fontSize: 11, + lineHeight: 12, + hideOverlap: true, + padding: [0, 5], + }, + }, + yAxis: totalData.length === 0 ? undefined : { + position: 'left', + name: this.widget ? undefined : yAxisName, + nameLocation: 'middle', + nameRotate: 90, + nameGap: 55, + nameTextStyle: { + fontSize: 11, + color: 'rgb(110, 112, 121)' + }, + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: (val): string => { + if (this.relativeMode) { + return `${val}%`; + } + if (this.unitGroupForm.controls.unitType.value === 'vb') { + return this.vbytesPipe.transform(val, 0, 'vB', undefined, true); + } + const selectedPowerOfTen: any = selectPowerOfTen(val); + const newVal = Math.round(val / selectedPowerOfTen.divider); + return `${newVal}${selectedPowerOfTen.unit}`; + }, + }, + splitLine: { + lineStyle: { + type: 'dotted', + color: 'var(--transparent-fg)', + opacity: 0.25, + } + }, + type: 'value', + }, + legend: (this.widget || totalData.length === 0 || !filtered) ? undefined : { + top: 'top', + data: [ + { + name: this.allLabel, + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { color: 'var(--fg)' }, + icon: 'roundRect', + }, + { + name: this.matchedLabel, + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { color: 'var(--fg)' }, + icon: 'roundRect', + }, + ], + selected: JSON.parse(this.storageService.getValue('goggles_legend') || 'null') ?? { + [this.allLabel]: true, + [this.matchedLabel]: true, + }, + }, + series: totalData.length === 0 ? undefined : [ + { + id: 'total', + zlevel: 0, + name: filtered ? this.allLabel : this.transactionsLabel, + data: totalData, + type: 'bar', + barWidth: '100%', + cursor: perBlock ? 'pointer' : 'default', + itemStyle: { color: '#1E88E5' }, // blue: total tx count + }, + ...(filtered && matchedData.length > 0 ? [{ + id: 'matched', + zlevel: 1, + z: 3, + name: this.matchedLabel, + data: matchedData, + type: 'bar', + barWidth: '100%', + barGap: '-100%', // overlay directly on top of the total bars + cursor: perBlock ? 'pointer' : 'default', + itemStyle: { color: '#8E24AA' }, + }] : []), + ], + dataZoom: this.widget ? null : [{ + type: 'inside', + realtime: true, + zoomLock: true, + maxSpan: 100, + minSpan: 5, + moveOnMouseMove: false, + }, { + showDetail: false, + show: true, + type: 'slider', + brushSelect: false, + realtime: true, + left: 20, + right: 15, + selectedDataBackground: { + lineStyle: { + color: '#fff', + opacity: 0.45, + }, + areaStyle: { + opacity: 0, + } + }, + }], + }; + } + + onChartInit(ec): void { + if (this.chartInstance !== undefined) { + return; + } + + this.chartInstance = ec; + + this.chartInstance.on('click', (e) => { + if (e.data.bucketSize > 1) { + return; + } + this.zone.run(() => { + const url = new RelativeUrlPipe(this.stateService).transform(`/block/${e.data.startHeight}`); + this.router.navigate([url]); + }); + }); + + this.chartInstance.on('legendselectchanged', (e) => { + this.storageService.setValue('goggles_legend', JSON.stringify(e.selected)); + }); + } + + isMobile(): boolean { + return (window.innerWidth <= 767.98); + } + + // the bucket sizes a given interval can be viewed at (24h is per-block only; longer ranges are week/month) + bucketSizesForInterval(interval: string): number[] { + return INTERVAL_PRESETS[interval] ?? [1008, 4032]; + } + + // for the template: the bucket options for the currently selected interval (used to show/hide the selector) + get availableBucketSizes(): number[] { + return this.bucketSizesForInterval(this.radioGroupForm.controls.dateSpan.value); + } + + get isFiltered(): boolean { + return !!this.goggle$.value.mask; + } + + onSaveChart(): void { + // @ts-ignore + const prevBottom = this.chartOptions.grid.bottom; + // @ts-ignore + const prevTitle = { ...this.chartOptions.title ?? {text: ''}}; + // @ts-ignore + const prevYAxisNameStyle: any = { ...this.chartOptions.yAxis.nameTextStyle}; + const now = new Date(); + const { op, mask } = this.goggle$.value; + const filters = mask ? toFilters(mask).map(f => f.label) : []; + if (this.chartOptions.legend) { + const currentLegend = this.chartInstance.getOption().legend; + if (currentLegend?.[0]?.selected) { + // @ts-ignore + this.chartOptions.legend.selected = currentLegend[0].selected; + } + } + // @ts-ignore + this.chartOptions.grid.bottom = 90; + this.chartOptions.backgroundColor = 'var(--active-bg)'; + const bucket = this.bucketGroupForm.controls.bucketSize.value; + const bucketSuffix = bucket === 1008 + ? $localize`Weekly average` + : bucket === 4032 + ? $localize`Monthly average` + : ''; + let expression = ''; + if (filters.length && this.chartOptions.xAxis) { + if (op === 'nor') { + expression += $localize`matching none of: `; + } else if (op === 'or') { + expression += $localize`matching any of: `; + } else { + expression += $localize`matching all of: `; + } + expression += filters.length > 1 ? filters.join(' - ') : filters[0]; + } + const text = `${bucketSuffix} ${$localize`of transactions`} ${expression}`; + this.chartOptions.title = { + text, + textStyle: { color: 'white', fontSize: 15, fontWeight: 'normal' }, + left: 'center', + bottom: 15, + }; + // @ts-ignore + this.chartOptions.yAxis.nameTextStyle = { + fontSize: 14, + color: 'white', + }; + this.chartInstance.setOption(this.chartOptions); + download(this.chartInstance.getDataURL({ + pixelRatio: 2, + excludeComponents: ['dataZoom'], + }), `block-goggles-${this.timespan}${mask ? `-${op}-${mask.toString()}` : ''}-${Math.round(now.getTime() / 1000)}.svg`); + // @ts-ignore + this.chartOptions.grid.bottom = prevBottom; + this.chartOptions.backgroundColor = 'none'; + this.chartOptions.title = prevTitle; + // @ts-ignore + this.chartOptions.yAxis.nameTextStyle = prevYAxisNameStyle; + this.chartInstance.setOption(this.chartOptions); + } +} diff --git a/frontend/src/app/components/graphs/graphs.component.html b/frontend/src/app/components/graphs/graphs.component.html index 74e37d298..4490d964b 100644 --- a/frontend/src/app/components/graphs/graphs.component.html +++ b/frontend/src/app/components/graphs/graphs.component.html @@ -3,6 +3,9 @@ Mempool + Goggles +
    diff --git a/frontend/src/app/graphs/graphs.module.ts b/frontend/src/app/graphs/graphs.module.ts index 5994a1775..737d3a5fa 100644 --- a/frontend/src/app/graphs/graphs.module.ts +++ b/frontend/src/app/graphs/graphs.module.ts @@ -9,6 +9,7 @@ import { BlockFeesSubsidyGraphComponent } from '@components/block-fees-subsidy-g import { PriceChartComponent } from '@components/price-chart/price-chart.component'; import { BlockRewardsGraphComponent } from '@components/block-rewards-graph/block-rewards-graph.component'; import { BlockFeeRatesGraphComponent } from '@components/block-fee-rates-graph/block-fee-rates-graph.component'; +import { BlockGogglesGraphComponent } from '@components/block-goggles-graph/block-goggles-graph.component'; import { BlockSizesWeightsGraphComponent } from '@components/block-sizes-weights-graph/block-sizes-weights-graph.component'; import { FeeDistributionGraphComponent } from '@components/fee-distribution-graph/fee-distribution-graph.component'; import { IncomingTransactionsGraphComponent } from '@components/incoming-transactions-graph/incoming-transactions-graph.component'; @@ -70,6 +71,7 @@ import { CommonModule } from '@angular/common'; PriceChartComponent, BlockRewardsGraphComponent, BlockFeeRatesGraphComponent, + BlockGogglesGraphComponent, BlockSizesWeightsGraphComponent, FeeDistributionGraphComponent, IncomingTransactionsGraphComponent, diff --git a/frontend/src/app/graphs/graphs.routing.module.ts b/frontend/src/app/graphs/graphs.routing.module.ts index f0399f410..91c43ecdb 100644 --- a/frontend/src/app/graphs/graphs.routing.module.ts +++ b/frontend/src/app/graphs/graphs.routing.module.ts @@ -25,6 +25,7 @@ import { AccelerationsListComponent } from '@components/acceleration/acceleratio import { AddressComponent } from '@components/address/address.component'; import { WalletComponent } from '@components/wallet/wallet.component'; import { CalculatorComponent } from '@components/calculator/calculator.component'; +import { BlockGogglesGraphComponent } from '@components/block-goggles-graph/block-goggles-graph.component'; const browserWindow = window || {}; // @ts-ignore @@ -114,6 +115,11 @@ const routes: Routes = [ data: { networks: ['bitcoin', 'liquid'] }, component: StatisticsComponent, }, + { + path: 'goggles', + data: { networks: [ 'bitcoin' ]}, + component: BlockGogglesGraphComponent, + }, { path: 'mining/hashrate-difficulty', data: { networks: ['bitcoin'] }, diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index d05b02c66..11be60f22 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -407,6 +407,13 @@ export class ApiService { ); } + getHistoricalTxCountByFlags$(interval: string, bucketSize: string, op?: string, mask?: string) : Observable> { + return this.httpClient.get( + this.apiBaseUrl + this.apiBasePath + `/api/v1/goggles/${interval}/${bucketSize}` + + (op !== undefined && mask !== undefined ? `/${op}/${mask}` : ''), { observe: 'response' } + ); + } + getBlockAudit$(hash: string) : Observable { this.setBlockAuditLoaded(hash); return this.httpClient.get( From a57c7ac8bb614d0691b8558538791ec43cb41590 Mon Sep 17 00:00:00 2001 From: rodribp Date: Mon, 15 Jun 2026 01:19:21 -0600 Subject: [PATCH 366/369] fix: remove and hide loading indicators from unnecesary components --- .../acceleration-fees-graph.component.html | 1 - .../components/address-graph/address-graph.component.html | 1 - .../block-fee-rates-graph.component.html | 2 +- .../block-fees-graph/block-fees-graph.component.html | 2 +- .../block-fees-subsidy-graph.component.html | 2 +- .../block-health-graph/block-health-graph.component.html | 2 +- .../block-rewards-graph/block-rewards-graph.component.html | 2 +- .../block-sizes-weights-graph.component.html | 2 ++ .../app/components/blocks-list/blocks-list.component.html | 2 +- .../indexing-progress/indexing-progress.component.html | 6 +++--- .../indexing-progress/indexing-progress.component.ts | 6 +++++- .../app/components/pool-ranking/pool-ranking.component.html | 2 +- frontend/src/app/components/pool/pool.component.html | 2 +- .../app/components/price-chart/price-chart.component.html | 1 - .../treasuries-graph/treasuries-graph.component.html | 1 - .../src/app/components/utxo-graph/utxo-graph.component.html | 1 - 16 files changed, 18 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html index 0e56ce992..e2d2b05d0 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html @@ -1,4 +1,3 @@ -
    diff --git a/frontend/src/app/components/address-graph/address-graph.component.html b/frontend/src/app/components/address-graph/address-graph.component.html index c9dd072c8..752db18ad 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.html +++ b/frontend/src/app/components/address-graph/address-graph.component.html @@ -1,4 +1,3 @@ -
    diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html index 76da8ce0d..5a232bbcb 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html index ff9629202..87cbbfe5c 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html index de242706c..e13bdc7ad 100644 --- a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html +++ b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/block-health-graph/block-health-graph.component.html b/frontend/src/app/components/block-health-graph/block-health-graph.component.html index aa9771d68..ae437a3b0 100644 --- a/frontend/src/app/components/block-health-graph/block-health-graph.component.html +++ b/frontend/src/app/components/block-health-graph/block-health-graph.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html index 7dcd90256..149ec4660 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html index e036d8552..550877615 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html @@ -1,3 +1,5 @@ + +
    diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 737e636c0..0583f03f9 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/indexing-progress/indexing-progress.component.html b/frontend/src/app/components/indexing-progress/indexing-progress.component.html index 783566744..9625f5314 100644 --- a/frontend/src/app/components/indexing-progress/indexing-progress.component.html +++ b/frontend/src/app/components/indexing-progress/indexing-progress.component.html @@ -1,3 +1,3 @@ - - - + + + diff --git a/frontend/src/app/components/indexing-progress/indexing-progress.component.ts b/frontend/src/app/components/indexing-progress/indexing-progress.component.ts index c81f7544b..becf12531 100644 --- a/frontend/src/app/components/indexing-progress/indexing-progress.component.ts +++ b/frontend/src/app/components/indexing-progress/indexing-progress.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'app-indexing-progress', @@ -7,6 +7,10 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; standalone: false, }) export class IndexingProgressComponent implements OnInit { + @Input() showBlocks: boolean = true; + @Input() showNetworkHashrate: boolean = true; + @Input() showPoolsHashrate: boolean = true; + constructor( ) {} diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.html b/frontend/src/app/components/pool-ranking/pool-ranking.component.html index 69682b560..33e120e79 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.html +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 7135e5a21..55a6fc75a 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/frontend/src/app/components/price-chart/price-chart.component.html b/frontend/src/app/components/price-chart/price-chart.component.html index 488e0c40b..be2cef7bc 100644 --- a/frontend/src/app/components/price-chart/price-chart.component.html +++ b/frontend/src/app/components/price-chart/price-chart.component.html @@ -1,4 +1,3 @@ -
    diff --git a/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.html b/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.html index c9dd072c8..752db18ad 100644 --- a/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.html +++ b/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.html @@ -1,4 +1,3 @@ -
    diff --git a/frontend/src/app/components/utxo-graph/utxo-graph.component.html b/frontend/src/app/components/utxo-graph/utxo-graph.component.html index 462e4328e..e8436245d 100644 --- a/frontend/src/app/components/utxo-graph/utxo-graph.component.html +++ b/frontend/src/app/components/utxo-graph/utxo-graph.component.html @@ -1,4 +1,3 @@ -
    From 72271d397d13ecb57807646f3e993f7f1931a29f Mon Sep 17 00:00:00 2001 From: rodribp Date: Mon, 3 Aug 2026 14:28:15 -0600 Subject: [PATCH 367/369] fix: stacked loading indicators --- .../indexing-progress.component.scss} | 11 +++++++++-- .../indexing-progress/indexing-progress.component.ts | 1 + .../loading-indicator.component.html | 2 +- .../loading-indicator/loading-indicator.component.ts | 1 - 4 files changed, 11 insertions(+), 4 deletions(-) rename frontend/src/app/components/{loading-indicator/loading-indicator.component.scss => indexing-progress/indexing-progress.component.scss} (75%) diff --git a/frontend/src/app/components/loading-indicator/loading-indicator.component.scss b/frontend/src/app/components/indexing-progress/indexing-progress.component.scss similarity index 75% rename from frontend/src/app/components/loading-indicator/loading-indicator.component.scss rename to frontend/src/app/components/indexing-progress/indexing-progress.component.scss index af84083b7..b0f7fb9de 100644 --- a/frontend/src/app/components/loading-indicator/loading-indicator.component.scss +++ b/frontend/src/app/components/indexing-progress/indexing-progress.component.scss @@ -1,16 +1,23 @@ -.sticky-loading { - position: absolute; +:host { + position: fixed; right: 10px; z-index: 1000; font-size: 14px; + + display: flex; + flex-wrap: wrap; + gap: 4px; + @media (width >= 992px) { left: 32px; top: 55px; } + @media (576px <= width < 992px ) { left: 18px; top: 55px; } + @media (width <= 575px) { left: 18px; top: 100px; diff --git a/frontend/src/app/components/indexing-progress/indexing-progress.component.ts b/frontend/src/app/components/indexing-progress/indexing-progress.component.ts index becf12531..3aecb8204 100644 --- a/frontend/src/app/components/indexing-progress/indexing-progress.component.ts +++ b/frontend/src/app/components/indexing-progress/indexing-progress.component.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core @Component({ selector: 'app-indexing-progress', templateUrl: './indexing-progress.component.html', + styleUrl: 'indexing-progress.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, }) diff --git a/frontend/src/app/components/loading-indicator/loading-indicator.component.html b/frontend/src/app/components/loading-indicator/loading-indicator.component.html index 378321701..4ebc7e140 100644 --- a/frontend/src/app/components/loading-indicator/loading-indicator.component.html +++ b/frontend/src/app/components/loading-indicator/loading-indicator.component.html @@ -1,3 +1,3 @@ -
    +
    {{ this.label }} ({{ progress }}%)
    \ No newline at end of file diff --git a/frontend/src/app/components/loading-indicator/loading-indicator.component.ts b/frontend/src/app/components/loading-indicator/loading-indicator.component.ts index 58ebf0a34..cf450ec90 100644 --- a/frontend/src/app/components/loading-indicator/loading-indicator.component.ts +++ b/frontend/src/app/components/loading-indicator/loading-indicator.component.ts @@ -7,7 +7,6 @@ import { WebsocketService } from '@app/services/websocket.service'; @Component({ selector: 'app-loading-indicator', templateUrl: './loading-indicator.component.html', - styleUrls: ['./loading-indicator.component.scss'], standalone: false, changeDetection: ChangeDetectionStrategy.OnPush }) From 7068fe6ae004c78fe2f67635c75043de84c19152 Mon Sep 17 00:00:00 2001 From: jramos0 Date: Tue, 11 Aug 2026 02:34:26 -0600 Subject: [PATCH 368/369] fix: viewing link in x app --- .../src/app/components/tracker/tracker.component.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/app/components/tracker/tracker.component.scss b/frontend/src/app/components/tracker/tracker.component.scss index 1ffc63e8f..e71e9d973 100644 --- a/frontend/src/app/components/tracker/tracker.component.scss +++ b/frontend/src/app/components/tracker/tracker.component.scss @@ -20,6 +20,14 @@ & > * { flex-shrink: 0; } + + // X’s iOS in-app browser overlays bottom controls without reporting a safe-area inset. + // Its incorrect svh value reveals the obscured strip via svh - dvh so correct browsers collapse it to zero. + &::after { + content: ''; + flex-shrink: 0; + height: max(0px, 100svh - 100dvh); + } } .heading { From a0e74fcf9e8d8a3b05a4f0639d881aa63b59609e Mon Sep 17 00:00:00 2001 From: rodribp Date: Tue, 11 Aug 2026 16:46:24 -0600 Subject: [PATCH 369/369] fix: use actual indexed block count for X-total-count header --- backend/src/api/bitcoin/bitcoin.routes.ts | 8 +++++--- backend/src/repositories/FlagValueRepository.ts | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 73741efbf..2388a5618 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -1176,16 +1176,18 @@ class BitcoinRoutes { const op = (req.params.op) as 'and' | 'or' | 'nor' | undefined; const mask = BigInt(req.params.mask ?? 0n); - const { tip } = await FlagValueRepository.$getTipAndTailIndexedByBucketSize(bucketSize) || { tip: undefined }; + const { tip, tail } = await FlagValueRepository.$getTipAndTailIndexedByBucketSize(bucketSize) || { tip: undefined, tail: undefined }; - if (!tip) { + if (tip === undefined || tail === undefined) { handleError(req, res, 400, `Failed to get latest indexed flag values for ${interval}`); return; } + const totalCount = await FlagValueRepository.$getTotalBlocksIndexedByBucketSize(bucketSize === 1 ? 1008 : bucketSize) ?? tip - tail; + const startHeight = presets[interval].retentionSpan !== -1 ? (tip - presets[interval].retentionSpan) : -1; const txsCount = await FlagValueRepository.$queryTxCountBasedOnMask(mask, bucketSize, op, startHeight); - res.header('X-total-count', tip.toString()); + res.header('X-total-count', totalCount.toString()); res.header('Expires', new Date(Date.now() + 1000 * 3600 * 24 * (presets[interval].bucketSizes[0] / 144)).toUTCString()); res.send(txsCount); } catch (e: any) { diff --git a/backend/src/repositories/FlagValueRepository.ts b/backend/src/repositories/FlagValueRepository.ts index 66f8495c1..9bedf7913 100644 --- a/backend/src/repositories/FlagValueRepository.ts +++ b/backend/src/repositories/FlagValueRepository.ts @@ -128,6 +128,18 @@ class FlagValuesRepository { logger.err(`Cannot delete flag values above ${height}. Reason: ` + (e instanceof Error ? e.message : e)); } } + + public async $getTotalBlocksIndexedByBucketSize(bucketSize: number): Promise { + try { + const [rows]: any[] = await DB.query(`SELECT (count(distinct start_height) * ?) as total FROM flag_values WHERE bucket_size = ?`, [bucketSize, bucketSize.toString()]); + if (rows !== null && rows.length > 0) { + return rows[0].total; + } + } catch (e) { + logger.err(`Cannot get total blocks indexed in flag_values. Reason: ` + (e instanceof Error ? e.message : e)); + } + return null; + } } export default new FlagValuesRepository();