diff --git a/LICENSE b/LICENSE
index b6a09390a..1c368c00a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,5 @@
 The Mempool Open Source Project®
-Copyright (c) 2019-2023 Mempool Space K.K. and other shadowy super-coders
+Copyright (c) 2019-2024 Mempool Space K.K. and other shadowy super-coders
 
 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU Affero General Public License as published by the Free
@@ -12,10 +12,12 @@ or any other contributor to The Mempool Open Source Project.
 
 The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, 
 Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full 
-Bitcoin ecosystem™, Mempool Goggles™, the mempool Logo, the mempool Square logo, 
-the mempool Blocks logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical 
-Logo, and the mempool.space Horizontal logo are registered trademarks or trademarks 
-of Mempool Space K.K in Japan, the United States, and/or other countries.
+Bitcoin ecosystem™, Mempool Goggles™, the mempool Logo, the mempool Square Logo, 
+the mempool block visualization Logo, the mempool Blocks Logo, the mempool 
+transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, 
+the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are 
+registered trademarks or trademarks of Mempool Space K.K in Japan, 
+the United States, and/or other countries.
 
 See our full Trademark Policy and Guidelines for more details, published on 
 <https://mempool.space/trademark-policy>.
diff --git a/backend/README.md b/backend/README.md
index 6ae4ae3e2..cecc07bc9 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -77,7 +77,7 @@ Query OK, 0 rows affected (0.00 sec)
 
 #### Build
 
-_Make sure to use Node.js 16.10 and npm 7._
+_Make sure to use Node.js 20.x and npm 9.x or newer_
 
 _The build process requires [Rust](https://www.rust-lang.org/tools/install) to be installed._
 
diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json
index 8cd7f2ede..60ff5e9fe 100644
--- a/backend/mempool-config.sample.json
+++ b/backend/mempool-config.sample.json
@@ -29,7 +29,7 @@
     "POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
     "POOLS_UPDATE_DELAY": 604800,
     "AUDIT": false,
-    "RUST_GBT": false,
+    "RUST_GBT": true,
     "LIMIT_GBT": false,
     "CPFP_INDEXING": false,
     "DISK_CACHE_BLOCK_INTERVAL": 6,
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 944abfdb2..7696eddd6 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -1,21 +1,22 @@
 {
   "name": "mempool-backend",
-  "version": "3.0.0-beta",
+  "version": "3.1.0-dev",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "mempool-backend",
-      "version": "3.0.0-beta",
+      "version": "3.1.0-dev",
       "hasInstallScript": true,
       "license": "GNU Affero General Public License v3.0",
       "dependencies": {
+        "@babel/core": "^7.25.2",
         "@mempool/electrum-client": "1.1.9",
         "@types/node": "^18.15.3",
-        "axios": "~1.7.2",
+        "axios": "1.7.2",
         "bitcoinjs-lib": "~6.1.3",
         "crypto-js": "~4.2.0",
-        "express": "~4.19.2",
+        "express": "~4.21.0",
         "maxmind": "~4.3.11",
         "mysql2": "~3.11.0",
         "redis": "^4.7.0",
@@ -2280,6 +2281,7 @@
       "version": "1.7.2",
       "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
       "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
+      "license": "MIT",
       "dependencies": {
         "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
@@ -2488,9 +2490,9 @@
       }
     },
     "node_modules/body-parser": {
-      "version": "1.20.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
-      "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+      "version": "1.20.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
       "dependencies": {
         "bytes": "3.1.2",
         "content-type": "~1.0.5",
@@ -2500,7 +2502,7 @@
         "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "on-finished": "2.4.1",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "raw-body": "2.5.2",
         "type-is": "~1.6.18",
         "unpipe": "1.0.0"
@@ -3029,9 +3031,9 @@
       "dev": true
     },
     "node_modules/encodeurl": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
       "engines": {
         "node": ">= 0.8"
       }
@@ -3459,36 +3461,36 @@
       }
     },
     "node_modules/express": {
-      "version": "4.19.2",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
-      "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
+      "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
       "dependencies": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.20.2",
+        "body-parser": "1.20.3",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
         "cookie": "0.6.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "1.2.0",
+        "finalhandler": "1.3.1",
         "fresh": "0.5.2",
         "http-errors": "2.0.0",
-        "merge-descriptors": "1.0.1",
+        "merge-descriptors": "1.0.3",
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.7",
+        "path-to-regexp": "0.1.10",
         "proxy-addr": "~2.0.7",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
-        "send": "0.18.0",
-        "serve-static": "1.15.0",
+        "send": "0.19.0",
+        "serve-static": "1.16.2",
         "setprototypeof": "1.2.0",
         "statuses": "2.0.1",
         "type-is": "~1.6.18",
@@ -3601,12 +3603,12 @@
       }
     },
     "node_modules/finalhandler": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
       "dependencies": {
         "debug": "2.6.9",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
@@ -6050,9 +6052,12 @@
       }
     },
     "node_modules/merge-descriptors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
     },
     "node_modules/merge-stream": {
       "version": "2.0.0",
@@ -6266,9 +6271,12 @@
       }
     },
     "node_modules/object-inspect": {
-      "version": "1.13.1",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
-      "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+      "engines": {
+        "node": ">= 0.4"
+      },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -6436,9 +6444,9 @@
       "dev": true
     },
     "node_modules/path-to-regexp": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+      "version": "0.1.10",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
     },
     "node_modules/path-type": {
       "version": "4.0.0",
@@ -6646,11 +6654,11 @@
       ]
     },
     "node_modules/qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
       "dependencies": {
-        "side-channel": "^1.0.4"
+        "side-channel": "^1.0.6"
       },
       "engines": {
         "node": ">=0.6"
@@ -6871,9 +6879,9 @@
       }
     },
     "node_modules/send": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
-      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "version": "0.19.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
       "dependencies": {
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -6906,6 +6914,14 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
     },
+    "node_modules/send/node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/send/node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6917,14 +6933,14 @@
       "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
     },
     "node_modules/serve-static": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
-      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
       "dependencies": {
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
-        "send": "0.18.0"
+        "send": "0.19.0"
       },
       "engines": {
         "node": ">= 0.8.0"
@@ -9603,9 +9619,9 @@
       }
     },
     "body-parser": {
-      "version": "1.20.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
-      "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+      "version": "1.20.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
       "requires": {
         "bytes": "3.1.2",
         "content-type": "~1.0.5",
@@ -9615,7 +9631,7 @@
         "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "on-finished": "2.4.1",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "raw-body": "2.5.2",
         "type-is": "~1.6.18",
         "unpipe": "1.0.0"
@@ -9996,9 +10012,9 @@
       "dev": true
     },
     "encodeurl": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
     },
     "error-ex": {
       "version": "1.3.2",
@@ -10303,36 +10319,36 @@
       }
     },
     "express": {
-      "version": "4.19.2",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
-      "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
+      "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
       "requires": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.20.2",
+        "body-parser": "1.20.3",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
         "cookie": "0.6.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "1.2.0",
+        "finalhandler": "1.3.1",
         "fresh": "0.5.2",
         "http-errors": "2.0.0",
-        "merge-descriptors": "1.0.1",
+        "merge-descriptors": "1.0.3",
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.7",
+        "path-to-regexp": "0.1.10",
         "proxy-addr": "~2.0.7",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
-        "send": "0.18.0",
-        "serve-static": "1.15.0",
+        "send": "0.19.0",
+        "serve-static": "1.16.2",
         "setprototypeof": "1.2.0",
         "statuses": "2.0.1",
         "type-is": "~1.6.18",
@@ -10434,12 +10450,12 @@
       }
     },
     "finalhandler": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
       "requires": {
         "debug": "2.6.9",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
@@ -12236,9 +12252,9 @@
       "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
     },
     "merge-descriptors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="
     },
     "merge-stream": {
       "version": "2.0.0",
@@ -12401,9 +12417,9 @@
       }
     },
     "object-inspect": {
-      "version": "1.13.1",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
-      "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ=="
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g=="
     },
     "on-finished": {
       "version": "2.4.1",
@@ -12520,9 +12536,9 @@
       "dev": true
     },
     "path-to-regexp": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+      "version": "0.1.10",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
     },
     "path-type": {
       "version": "4.0.0",
@@ -12664,11 +12680,11 @@
       "dev": true
     },
     "qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
       "requires": {
-        "side-channel": "^1.0.4"
+        "side-channel": "^1.0.6"
       }
     },
     "queue-microtask": {
@@ -12802,9 +12818,9 @@
       "dev": true
     },
     "send": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
-      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "version": "0.19.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
       "requires": {
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -12836,6 +12852,11 @@
             }
           }
         },
+        "encodeurl": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+          "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
+        },
         "ms": {
           "version": "2.1.3",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -12849,14 +12870,14 @@
       "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
     },
     "serve-static": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
-      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
       "requires": {
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
-        "send": "0.18.0"
+        "send": "0.19.0"
       }
     },
     "set-function-length": {
diff --git a/backend/package.json b/backend/package.json
index a5fb4bdbe..c18974021 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mempool-backend",
-  "version": "3.0.0-beta",
+  "version": "3.1.0-dev",
   "description": "Bitcoin mempool visualizer and blockchain explorer backend",
   "license": "GNU Affero General Public License v3.0",
   "homepage": "https://mempool.space",
@@ -42,10 +42,10 @@
     "@babel/core": "^7.25.2",
     "@mempool/electrum-client": "1.1.9",
     "@types/node": "^18.15.3",
-    "axios": "~1.7.2",
+    "axios": "1.7.2",
     "bitcoinjs-lib": "~6.1.3",
     "crypto-js": "~4.2.0",
-    "express": "~4.19.2",
+    "express": "~4.21.0",
     "maxmind": "~4.3.11",
     "mysql2": "~3.11.0",
     "rust-gbt": "file:./rust-gbt",
diff --git a/backend/src/__tests__/api/common.ts b/backend/src/__tests__/api/common.ts
index 74a7db88f..14ae3c78b 100644
--- a/backend/src/__tests__/api/common.ts
+++ b/backend/src/__tests__/api/common.ts
@@ -1,5 +1,5 @@
 import { Common } from '../../api/common';
-import { MempoolTransactionExtended } from '../../mempool.interfaces';
+import { MempoolTransactionExtended, TransactionExtended } from '../../mempool.interfaces';
 
 const randomTransactions = require('./test-data/transactions-random.json');
 const replacedTransactions = require('./test-data/transactions-replaced.json');
@@ -10,14 +10,14 @@ describe('Common', () => {
   describe('RBF', () => {
     const newTransactions = rbfTransactions.concat(randomTransactions);
     test('should detect RBF transactions with fast method', () => {
-      const result: { [txid: string]: MempoolTransactionExtended[] } = Common.findRbfTransactions(newTransactions, replacedTransactions);
+      const result: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }} = Common.findRbfTransactions(newTransactions, replacedTransactions);
       expect(Object.values(result).length).toEqual(2);
       expect(result).toHaveProperty('7219d95161f3718335991ac6d967d24eedec370908c9879bb1e192e6d797d0a6');
       expect(result).toHaveProperty('5387881d695d4564d397026dc5f740f816f8390b4b2c5ec8c20309122712a875');
     });
 
     test('should detect RBF transactions with scalable method', () => {
-      const result: { [txid: string]: MempoolTransactionExtended[] } = Common.findRbfTransactions(newTransactions, replacedTransactions, true);
+      const result: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }} = Common.findRbfTransactions(newTransactions, replacedTransactions, true);
       expect(Object.values(result).length).toEqual(2);
       expect(result).toHaveProperty('7219d95161f3718335991ac6d967d24eedec370908c9879bb1e192e6d797d0a6');
       expect(result).toHaveProperty('5387881d695d4564d397026dc5f740f816f8390b4b2c5ec8c20309122712a875');
diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts
index d112ab6eb..a71f0e2ad 100644
--- a/backend/src/__tests__/config.test.ts
+++ b/backend/src/__tests__/config.test.ts
@@ -43,7 +43,7 @@ describe('Mempool Backend Config', () => {
         POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json',
         POOLS_UPDATE_DELAY: 604800,
         AUDIT: false,
-        RUST_GBT: false,
+        RUST_GBT: true,
         LIMIT_GBT: false,
         CPFP_INDEXING: false,
         MAX_BLOCKS_BULK_QUERY: 0,
diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts
index eea96af69..e09234cdc 100644
--- a/backend/src/api/audit.ts
+++ b/backend/src/api/audit.ts
@@ -2,6 +2,7 @@ import config from '../config';
 import logger from '../logger';
 import { MempoolTransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces';
 import rbfCache from './rbf-cache';
+import transactionUtils from './transaction-utils';
 
 const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners
 
@@ -15,7 +16,8 @@ class Audit {
     const matches: string[] = []; // present in both mined block and template
     const added: string[] = []; // present in mined block, not in template
     const unseen: string[] = []; // present in the mined block, not in our mempool
-    const prioritized: string[] = []; // higher in the block than would be expected by in-band feerate alone
+    let prioritized: string[] = []; // higher in the block than would be expected by in-band feerate alone
+    let deprioritized: string[] = []; // lower in the block than would be expected by in-band feerate alone
     const fresh: string[] = []; // missing, but firstSeen or lastBoosted within PROPAGATION_MARGIN
     const rbf: string[] = []; // either missing or present, and either part of a full-rbf replacement, or a conflict with the mined block
     const accelerated: string[] = []; // prioritized by the mempool accelerator
@@ -133,23 +135,7 @@ class Audit {
       totalWeight += tx.weight;
     }
 
-
-    // identify "prioritized" transactions
-    let lastEffectiveRate = 0;
-    // Iterate over the mined template from bottom to top (excluding the coinbase)
-    // Transactions should appear in ascending order of mining priority.
-    for (let i = transactions.length - 1; i > 0; i--) {
-      const blockTx = transactions[i];
-      // If a tx has a lower in-band effective fee rate than the previous tx,
-      // it must have been prioritized out-of-band (in order to have a higher mining priority)
-      // so exclude from the analysis.
-      if ((blockTx.effectiveFeePerVsize || 0) < lastEffectiveRate) {
-        prioritized.push(blockTx.txid);
-        // accelerated txs may or may not have their prioritized fee rate applied, so don't use them as a reference
-      } else if (!isAccelerated[blockTx.txid]) {
-        lastEffectiveRate = blockTx.effectiveFeePerVsize || 0;
-      }
-    }
+    ({ prioritized, deprioritized } = transactionUtils.identifyPrioritizedTransactions(transactions, 'effectiveFeePerVsize'));
 
     // transactions missing from near the end of our template are probably not being censored
     let overflowWeightRemaining = overflowWeight - (config.MEMPOOL.BLOCK_WEIGHT_UNITS - totalWeight);
diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts
index 3e1fe2108..7fa431db6 100644
--- a/backend/src/api/bitcoin/bitcoin-api.ts
+++ b/backend/src/api/bitcoin/bitcoin-api.ts
@@ -323,6 +323,7 @@ class BitcoinApi implements AbstractBitcoinApi {
       'witness_v1_taproot': 'v1_p2tr',
       'nonstandard': 'nonstandard',
       'multisig': 'multisig',
+      'anchor': 'anchor',
       'nulldata': 'op_return'
     };
 
diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts
index 6225a9c1d..498003d98 100644
--- a/backend/src/api/bitcoin/bitcoin.routes.ts
+++ b/backend/src/api/bitcoin/bitcoin.routes.ts
@@ -20,6 +20,7 @@ import difficultyAdjustment from '../difficulty-adjustment';
 import transactionRepository from '../../repositories/TransactionRepository';
 import rbfCache from '../rbf-cache';
 import { calculateMempoolTxCpfp } from '../cpfp';
+import { handleError } from '../../utils/api';
 
 class BitcoinRoutes {
   public initRoutes(app: Application) {
@@ -86,7 +87,7 @@ class BitcoinRoutes {
       res.set('Content-Type', 'application/json');
       res.send(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -105,13 +106,13 @@ class BitcoinRoutes {
       const result = mempoolBlocks.getMempoolBlocks();
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private getTransactionTimes(req: Request, res: Response) {
     if (!Array.isArray(req.query.txId)) {
-      res.status(500).send('Not an array');
+      handleError(req, res, 500, 'Not an array');
       return;
     }
     const txIds: string[] = [];
@@ -128,12 +129,12 @@ class BitcoinRoutes {
   private async $getBatchedOutspends(req: Request, res: Response): Promise<IEsploraApi.Outspend[][] | void> {
     const txids_csv = req.query.txids;
     if (!txids_csv || typeof txids_csv !== 'string') {
-      res.status(500).send('Invalid txids format');
+      handleError(req, res, 500, 'Invalid txids format');
       return;
     }
     const txids = txids_csv.split(',');
     if (txids.length > 50) {
-      res.status(400).send('Too many txids requested');
+      handleError(req, res, 400, 'Too many txids requested');
       return;
     }
 
@@ -141,13 +142,13 @@ class BitcoinRoutes {
       const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txids);
       res.json(batchedOutspends);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async $getCpfpInfo(req: Request, res: Response) {
     if (!/^[a-fA-F0-9]{64}$/.test(req.params.txId)) {
-      res.status(501).send(`Invalid transaction ID.`);
+      handleError(req, res, 501, `Invalid transaction ID.`);
       return;
     }
 
@@ -180,7 +181,7 @@ class BitcoinRoutes {
         try {
           cpfpInfo = await transactionRepository.$getCpfpInfo(req.params.txId);
         } catch (e) {
-          res.status(500).send('failed to get CPFP info');
+          handleError(req, res, 500, 'failed to get CPFP info');
           return;
         }
       }
@@ -209,7 +210,7 @@ class BitcoinRoutes {
       if (e instanceof Error && e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
         statusCode = 404;
       }
-      res.status(statusCode).send(e instanceof Error ? e.message : e);
+      handleError(req, res, statusCode, e instanceof Error ? e.message : e);
     }
   }
 
@@ -223,7 +224,7 @@ class BitcoinRoutes {
       if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
         statusCode = 404;
       }
-      res.status(statusCode).send(e instanceof Error ? e.message : e);
+      handleError(req, res, statusCode, e instanceof Error ? e.message : e);
     }
   }
 
@@ -284,13 +285,13 @@ class BitcoinRoutes {
         // Not modified
         // 422 Unprocessable Entity
         // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
-        res.status(422).send(`Psbt had no missing nonWitnessUtxos.`);
+        handleError(req, res, 422, `Psbt had no missing nonWitnessUtxos.`);
       }
     } catch (e: any) {
       if (e instanceof Error && new RegExp(notFoundError).test(e.message)) {
-        res.status(404).send(e.message);
+        handleError(req, res, 404, e.message);
       } else {
-        res.status(500).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 500, e instanceof Error ? e.message : e);
       }
     }
   }
@@ -304,7 +305,7 @@ class BitcoinRoutes {
       if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
         statusCode = 404;
       }
-      res.status(statusCode).send(e instanceof Error ? e.message : e);
+      handleError(req, res, statusCode, e instanceof Error ? e.message : e);
     }
   }
 
@@ -314,7 +315,7 @@ class BitcoinRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
       res.json(transactions);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -336,7 +337,7 @@ class BitcoinRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * cacheDuration).toUTCString());
       res.json(block);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -346,7 +347,7 @@ class BitcoinRoutes {
       res.setHeader('content-type', 'text/plain');
       res.send(blockHeader);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -357,10 +358,11 @@ class BitcoinRoutes {
         res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
         res.json(auditSummary);
       } else {
-        return res.status(404).send(`audit not available`);
+        handleError(req, res, 404, `audit not available`);
+        return;
       }
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -371,7 +373,8 @@ class BitcoinRoutes {
         res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
         res.json(auditSummary);
       } else {
-        return res.status(404).send(`transaction audit not available`);
+        handleError(req, res, 404, `transaction audit not available`);
+        return;
       }
     } catch (e) {
       res.status(500).send(e instanceof Error ? e.message : e);
@@ -388,42 +391,49 @@ class BitcoinRoutes {
         return await this.getLegacyBlocks(req, res);
       }
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async getBlocksByBulk(req: Request, res: Response) {
     try {
       if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid - Not implemented
-        return res.status(404).send(`This API is only available for Bitcoin networks`);
+        handleError(req, res, 404, `This API is only available for Bitcoin networks`);
+        return;
       }
       if (config.MEMPOOL.MAX_BLOCKS_BULK_QUERY <= 0) {
-        return res.status(404).send(`This API is disabled. Set config.MEMPOOL.MAX_BLOCKS_BULK_QUERY to a positive number to enable it.`);
+        handleError(req, res, 404, `This API is disabled. Set config.MEMPOOL.MAX_BLOCKS_BULK_QUERY to a positive number to enable it.`);
+        return;
       }
       if (!Common.indexingEnabled()) {
-        return res.status(404).send(`Indexing is required for this API`);
+        handleError(req, res, 404, `Indexing is required for this API`);
+        return;
       }
 
       const from = parseInt(req.params.from, 10);
       if (!req.params.from || from < 0) {
-        return res.status(400).send(`Parameter 'from' must be a block height (integer)`);
+        handleError(req, res, 400, `Parameter 'from' must be a block height (integer)`);
+        return;
       }
       const to = req.params.to === undefined ? await bitcoinApi.$getBlockHeightTip() : parseInt(req.params.to, 10);
       if (to < 0) {
-        return res.status(400).send(`Parameter 'to' must be a block height (integer)`);
+        handleError(req, res, 400, `Parameter 'to' must be a block height (integer)`);
+        return;
       }
       if (from > to) {
-        return res.status(400).send(`Parameter 'to' must be a higher block height than 'from'`);
+        handleError(req, res, 400, `Parameter 'to' must be a higher block height than 'from'`);
+        return;
       }
       if ((to - from + 1) > config.MEMPOOL.MAX_BLOCKS_BULK_QUERY) {
-        return res.status(400).send(`You can only query ${config.MEMPOOL.MAX_BLOCKS_BULK_QUERY} blocks at once.`);
+        handleError(req, res, 400, `You can only query ${config.MEMPOOL.MAX_BLOCKS_BULK_QUERY} blocks at once.`);
+        return;
       }
 
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(await blocks.$getBlocksBetweenHeight(from, to));
 
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -458,10 +468,10 @@ class BitcoinRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(returnBlocks);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
-  
+
   private async getBlockTransactions(req: Request, res: Response) {
     try {
       loadingIndicators.setProgress('blocktxs-' + req.params.hash, 0);
@@ -483,7 +493,7 @@ class BitcoinRoutes {
       res.json(transactions);
     } catch (e) {
       loadingIndicators.setProgress('blocktxs-' + req.params.hash, 100);
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -492,13 +502,13 @@ class BitcoinRoutes {
       const blockHash = await bitcoinApi.$getBlockHash(parseInt(req.params.height, 10));
       res.send(blockHash);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async getAddress(req: Request, res: Response) {
     if (config.MEMPOOL.BACKEND === 'none') {
-      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
+      handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
       return;
     }
 
@@ -507,15 +517,16 @@ class BitcoinRoutes {
       res.json(addressData);
     } catch (e) {
       if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
-        return res.status(413).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 413, e instanceof Error ? e.message : e);
+        return;
       }
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async getAddressTransactions(req: Request, res: Response): Promise<void> {
     if (config.MEMPOOL.BACKEND === 'none') {
-      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
+      handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
       return;
     }
 
@@ -528,23 +539,23 @@ class BitcoinRoutes {
       res.json(transactions);
     } catch (e) {
       if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
-        res.status(413).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 413, e instanceof Error ? e.message : e);
         return;
       }
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async getAddressTransactionSummary(req: Request, res: Response): Promise<void> {
     if (config.MEMPOOL.BACKEND !== 'esplora') {
-      res.status(405).send('Address summary lookups require mempool/electrs backend.');
+      handleError(req, res, 405, 'Address summary lookups require mempool/electrs backend.');
       return;
     }
   }
 
   private async getScriptHash(req: Request, res: Response) {
     if (config.MEMPOOL.BACKEND === 'none') {
-      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
+      handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
       return;
     }
 
@@ -555,15 +566,16 @@ class BitcoinRoutes {
       res.json(addressData);
     } catch (e) {
       if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
-        return res.status(413).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 413, e instanceof Error ? e.message : e);
+        return;
       }
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async getScriptHashTransactions(req: Request, res: Response): Promise<void> {
     if (config.MEMPOOL.BACKEND === 'none') {
-      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
+      handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
       return;
     }
 
@@ -578,16 +590,16 @@ class BitcoinRoutes {
       res.json(transactions);
     } catch (e) {
       if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
-        res.status(413).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 413, e instanceof Error ? e.message : e);
         return;
       }
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async getScriptHashTransactionSummary(req: Request, res: Response): Promise<void> {
     if (config.MEMPOOL.BACKEND !== 'esplora') {
-      res.status(405).send('Scripthash summary lookups require mempool/electrs backend.');
+      handleError(req, res, 405, 'Scripthash summary lookups require mempool/electrs backend.');
       return;
     }
   }
@@ -597,7 +609,7 @@ class BitcoinRoutes {
       const blockHash = await bitcoinApi.$getAddressPrefix(req.params.prefix);
       res.send(blockHash);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -624,7 +636,7 @@ class BitcoinRoutes {
       const rawMempool = await bitcoinApi.$getRawMempool();
       res.send(rawMempool);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -632,12 +644,13 @@ class BitcoinRoutes {
     try {
       const result = blocks.getCurrentBlockHeight();
       if (!result) {
-        return res.status(503).send(`Service Temporarily Unavailable`);
+        handleError(req, res, 503, `Service Temporarily Unavailable`);
+        return;
       }
       res.setHeader('content-type', 'text/plain');
       res.send(result.toString());
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -647,7 +660,7 @@ class BitcoinRoutes {
       res.setHeader('content-type', 'text/plain');
       res.send(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -657,7 +670,7 @@ class BitcoinRoutes {
       res.setHeader('content-type', 'application/octet-stream');
       res.send(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -666,7 +679,7 @@ class BitcoinRoutes {
       const result = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -675,7 +688,7 @@ class BitcoinRoutes {
       const result = await bitcoinClient.validateAddress(req.params.address);
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -688,7 +701,7 @@ class BitcoinRoutes {
         replaces
       });
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -697,7 +710,7 @@ class BitcoinRoutes {
       const result = rbfCache.getRbfTrees(false);
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -706,7 +719,7 @@ class BitcoinRoutes {
       const result = rbfCache.getRbfTrees(true);
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -719,7 +732,7 @@ class BitcoinRoutes {
         res.status(204).send();
       }
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -728,7 +741,7 @@ class BitcoinRoutes {
       const result = await bitcoinApi.$getOutspends(req.params.txId);
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -738,10 +751,10 @@ class BitcoinRoutes {
       if (da) {
         res.json(da);
       } else {
-        res.status(503).send(`Service Temporarily Unavailable`);
+        handleError(req, res, 503, `Service Temporarily Unavailable`);
       }
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -752,7 +765,7 @@ class BitcoinRoutes {
       const txIdResult = await bitcoinApi.$sendRawTransaction(rawTx);
       res.send(txIdResult);
     } catch (e: any) {
-      res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
+      handleError(req, res, 400, e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
         : (e.message || 'Error'));
     }
   }
@@ -764,7 +777,7 @@ class BitcoinRoutes {
       const txIdResult = await bitcoinClient.sendRawTransaction(txHex);
       res.send(txIdResult);
     } catch (e: any) {
-      res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
+      handleError(req, res, 400, e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
         : (e.message || 'Error'));
     }
   }
@@ -776,8 +789,7 @@ class BitcoinRoutes {
       const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate);
       res.send(result);
     } catch (e: any) {
-      res.setHeader('content-type', 'text/plain');
-      res.status(400).send(e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
+      handleError(req, res, 400, e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
         : (e.message || 'Error'));
     }
   }
diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts
index a5b8af0e2..9a7d8b11a 100644
--- a/backend/src/api/blocks.ts
+++ b/backend/src/api/blocks.ts
@@ -34,6 +34,7 @@ import { calculateFastBlockCpfp, calculateGoodBlockCpfp } from './cpfp';
 import mempool from './mempool';
 import CpfpRepository from '../repositories/CpfpRepository';
 import accelerationApi from './services/acceleration';
+import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
 
 class Blocks {
   private blocks: BlockExtended[] = [];
@@ -219,10 +220,10 @@ class Blocks {
     };
   }
 
-  public summarizeBlockTransactions(hash: string, transactions: TransactionExtended[]): BlockSummary {
+  public summarizeBlockTransactions(hash: string, height: number, transactions: TransactionExtended[]): BlockSummary {
     return {
       id: hash,
-      transactions: Common.classifyTransactions(transactions),
+      transactions: Common.classifyTransactions(transactions, height),
     };
   }
 
@@ -342,7 +343,12 @@ class Blocks {
           id: pool.uniqueId,
           name: pool.name,
           slug: pool.slug,
+          minerNames: null,
         };
+
+        if (extras.pool.name === 'OCEAN') {
+          extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
+        }
       }
 
       extras.matchRate = null;
@@ -616,7 +622,7 @@ class Blocks {
           // add CPFP
           const cpfpSummary = calculateGoodBlockCpfp(height, txs, []);
           // classify
-          const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, cpfpSummary.transactions);
+          const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, height, cpfpSummary.transactions);
           await BlocksSummariesRepository.$saveTransactions(height, blockHash, classifiedTxs, 2);
           if (unclassifiedBlocks[height].version < 2 && targetSummaryVersion === 2) {
             const cpfpClusters = await CpfpRepository.$getClustersAt(height);
@@ -653,7 +659,7 @@ class Blocks {
             }
             const cpfpSummary = calculateGoodBlockCpfp(height, templateTxs?.filter(tx => tx['effectiveFeePerVsize'] != null) as MempoolTransactionExtended[], []);
             // classify
-            const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, cpfpSummary.transactions);
+            const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, height, cpfpSummary.transactions);
             const classifiedTxMap: { [txid: string]: TransactionClassified } = {};
             for (const tx of classifiedTxs) {
               classifiedTxMap[tx.txid] = tx;
@@ -912,7 +918,7 @@ class Blocks {
       }
       const cpfpSummary: CpfpSummary = calculateGoodBlockCpfp(block.height, transactions, accelerations.map(a => ({ txid: a.txid, max_bid: a.feeDelta })));
       const blockExtended: BlockExtended = await this.$getBlockExtended(block, cpfpSummary.transactions);
-      const blockSummary: BlockSummary = this.summarizeBlockTransactions(block.id, cpfpSummary.transactions);
+      const blockSummary: BlockSummary = this.summarizeBlockTransactions(block.id, block.height, cpfpSummary.transactions);
       this.updateTimerProgress(timer, `got block data for ${this.currentBlockHeight}`);
 
       if (Common.indexingEnabled()) {
@@ -1169,7 +1175,7 @@ class Blocks {
         transactions: cpfpSummary.transactions.map(tx => {
           let flags: number = 0;
           try {
-            flags = Common.getTransactionFlags(tx);
+            flags = Common.getTransactionFlags(tx, height);
           } catch (e) {
             logger.warn('Failed to classify transaction: ' + (e instanceof Error ? e.message : e));
           }
@@ -1188,7 +1194,7 @@ class Blocks {
     } else {
       if (config.MEMPOOL.BACKEND === 'esplora') {
         const txs = (await bitcoinApi.$getTxsForBlock(hash)).map(tx => transactionUtils.extendTransaction(tx));
-        summary = this.summarizeBlockTransactions(hash, txs);
+        summary = this.summarizeBlockTransactions(hash, height || 0, txs);
         summaryVersion = 1;
       } else {
         // Call Core RPC
@@ -1324,7 +1330,7 @@ class Blocks {
           let summaryVersion = 0;
           if (config.MEMPOOL.BACKEND === 'esplora') {
             const txs = (await bitcoinApi.$getTxsForBlock(cleanBlock.hash)).map(tx => transactionUtils.extendTransaction(tx));
-            summary = this.summarizeBlockTransactions(cleanBlock.hash, txs);
+            summary = this.summarizeBlockTransactions(cleanBlock.hash, cleanBlock.height, txs);
             summaryVersion = 1;
           } else {
             // Call Core RPC
diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts
index 13fc86147..50de63afc 100644
--- a/backend/src/api/common.ts
+++ b/backend/src/api/common.ts
@@ -10,7 +10,6 @@ import logger from '../logger';
 import { getVarIntLength, opcodes, parseMultisigScript } from '../utils/bitcoin-script';
 
 // Bitcoin Core default policy settings
-const TX_MAX_STANDARD_VERSION = 2;
 const MAX_STANDARD_TX_WEIGHT = 400_000;
 const MAX_BLOCK_SIGOPS_COST = 80_000;
 const MAX_STANDARD_TX_SIGOPS_COST = (MAX_BLOCK_SIGOPS_COST / 5);
@@ -80,8 +79,8 @@ export class Common {
     return arr;
   }
 
-  static findRbfTransactions(added: MempoolTransactionExtended[], deleted: MempoolTransactionExtended[], forceScalable = false): { [txid: string]: MempoolTransactionExtended[] } {
-    const matches: { [txid: string]: MempoolTransactionExtended[] } = {};
+  static findRbfTransactions(added: MempoolTransactionExtended[], deleted: MempoolTransactionExtended[], forceScalable = false): { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }} {
+    const matches: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }} = {};
 
     // For small N, a naive nested loop is extremely fast, but it doesn't scale
     if (added.length < 1000 && deleted.length < 50 && !forceScalable) {
@@ -96,7 +95,7 @@ export class Common {
               addedTx.vin.some((vin) => vin.txid === deletedVin.txid && vin.vout === deletedVin.vout));
             });
         if (foundMatches?.length) {
-          matches[addedTx.txid] = [...new Set(foundMatches)];
+          matches[addedTx.txid] = { replaced: [...new Set(foundMatches)], replacedBy: addedTx };
         }
       });
     } else {
@@ -124,7 +123,7 @@ export class Common {
             foundMatches.add(deletedTx);
           }
           if (foundMatches.size) {
-            matches[addedTx.txid] = [...foundMatches];
+            matches[addedTx.txid] = { replaced: [...foundMatches], replacedBy: addedTx };
           }
         }
       }
@@ -139,17 +138,17 @@ export class Common {
       const replaced: Set<MempoolTransactionExtended> = new Set();
       for (let i = 0; i < tx.vin.length; i++) {
         const vin = tx.vin[i];
-        const match = spendMap.get(`${vin.txid}:${vin.vout}`);
+        const key = `${vin.txid}:${vin.vout}`;
+        const match = spendMap.get(key);
         if (match && match.txid !== tx.txid) {
           replaced.add(match);
           // remove this tx from the spendMap
           // prevents the same tx being replaced more than once
           for (const replacedVin of match.vin) {
-            const key = `${replacedVin.txid}:${replacedVin.vout}`;
-            spendMap.delete(key);
+            const replacedKey = `${replacedVin.txid}:${replacedVin.vout}`;
+            spendMap.delete(replacedKey);
           }
         }
-        const key = `${vin.txid}:${vin.vout}`;
         spendMap.delete(key);
       }
       if (replaced.size) {
@@ -200,10 +199,13 @@ export class Common {
    *
    * returns true early if any standardness rule is violated, otherwise false
    * (except for non-mandatory-script-verify-flag and p2sh script evaluation rules which are *not* enforced)
+   *
+   * As standardness rules change, we'll need to apply the rules in force *at the time* to older blocks.
+   * For now, just pull out individual rules into versioned functions where necessary.
    */
-  static isNonStandard(tx: TransactionExtended): boolean {
+  static isNonStandard(tx: TransactionExtended, height?: number): boolean {
     // version
-    if (tx.version > TX_MAX_STANDARD_VERSION) {
+    if (this.isNonStandardVersion(tx, height)) {
       return true;
     }
 
@@ -250,6 +252,8 @@ export class Common {
         }
       } else if (['unknown', 'provably_unspendable', 'empty'].includes(vin.prevout?.scriptpubkey_type || '')) {
         return true;
+      } else if (this.isNonStandardAnchor(tx, height)) {
+        return true;
       }
       // TODO: bad-witness-nonstandard
     }
@@ -335,6 +339,49 @@ export class Common {
     return false;
   }
 
+  // Individual versioned standardness rules
+
+  static V3_STANDARDNESS_ACTIVATION_HEIGHT = {
+    'testnet4': 42_000,
+    'testnet': 2_900_000,
+    'signet': 211_000,
+    '': 863_500,
+  };
+  static isNonStandardVersion(tx: TransactionExtended, height?: number): boolean {
+    let TX_MAX_STANDARD_VERSION = 3;
+    if (
+      height != null
+      && this.V3_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
+      && height <= this.V3_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
+    ) {
+      // V3 transactions were non-standard to spend before v28.x (scheduled for 2024/09/30 https://github.com/bitcoin/bitcoin/issues/29891)
+      TX_MAX_STANDARD_VERSION = 2;
+    }
+
+    if (tx.version > TX_MAX_STANDARD_VERSION) {
+      return true;
+    }
+    return false;
+  }
+
+  static ANCHOR_STANDARDNESS_ACTIVATION_HEIGHT = {
+    'testnet4': 42_000,
+    'testnet': 2_900_000,
+    'signet': 211_000,
+    '': 863_500,
+  };
+  static isNonStandardAnchor(tx: TransactionExtended, height?: number): boolean {
+    if (
+      height != null
+      && this.ANCHOR_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
+      && height <= this.ANCHOR_STANDARDNESS_ACTIVATION_HEIGHT[config.MEMPOOL.NETWORK]
+    ) {
+      // anchor outputs were non-standard to spend before v28.x (scheduled for 2024/09/30 https://github.com/bitcoin/bitcoin/issues/29891)
+      return true;
+    }
+    return false;
+  }
+
   static getNonWitnessSize(tx: TransactionExtended): number {
     let weight = tx.weight;
     let hasWitness = false;
@@ -415,7 +462,7 @@ export class Common {
     return flags;
   }
 
-  static getTransactionFlags(tx: TransactionExtended): number {
+  static getTransactionFlags(tx: TransactionExtended, height?: number): number {
     let flags = tx.flags ? BigInt(tx.flags) : 0n;
 
     // Update variable flags (CPFP, RBF)
@@ -548,7 +595,7 @@ export class Common {
     if (hasFakePubkey) {
       flags |= TransactionFlags.fake_pubkey;
     }
-    
+
     // fast but bad heuristic to detect possible coinjoins
     // (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse)
     const addressReuse = Object.keys(reusedOutputAddresses).reduce((acc, key) => Math.max(acc, (reusedInputAddresses[key] || 0) + (reusedOutputAddresses[key] || 0)), 0) > 1;
@@ -564,17 +611,17 @@ export class Common {
       flags |= TransactionFlags.batch_payout;
     }
 
-    if (this.isNonStandard(tx)) {
+    if (this.isNonStandard(tx, height)) {
       flags |= TransactionFlags.nonstandard;
     }
 
     return Number(flags);
   }
 
-  static classifyTransaction(tx: TransactionExtended): TransactionClassified {
+  static classifyTransaction(tx: TransactionExtended, height?: number): TransactionClassified {
     let flags = 0;
     try {
-      flags = Common.getTransactionFlags(tx);
+      flags = Common.getTransactionFlags(tx, height);
     } catch (e) {
       logger.warn('Failed to add classification flags to transaction: ' + (e instanceof Error ? e.message : e));
     }
@@ -585,8 +632,8 @@ export class Common {
     };
   }
 
-  static classifyTransactions(txs: TransactionExtended[]): TransactionClassified[] {
-    return txs.map(Common.classifyTransaction);
+  static classifyTransactions(txs: TransactionExtended[], height?: number): TransactionClassified[] {
+    return txs.map(tx => Common.classifyTransaction(tx, height));
   }
 
   static stripTransaction(tx: TransactionExtended): TransactionStripped {
diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts
index 6ddca7697..95f8c8707 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 = 81;
+  private static currentVersion = 82;
   private queryTimeout = 3600_000;
   private statisticsAddedIndexed = false;
   private uniqueLogs: string[] = [];
@@ -700,6 +700,11 @@ class DatabaseMigration {
       await this.$executeQuery('ALTER TABLE `blocks_audits` ADD unseen_txs JSON DEFAULT "[]"');
       await this.updateToSchemaVersion(81);
     }
+
+    if (databaseSchemaVersion < 82 && isBitcoin === true && config.MEMPOOL.NETWORK === 'mainnet') {
+      await this.$fixBadV1AuditBlocks();
+      await this.updateToSchemaVersion(82);
+    }
   }
 
   /**
@@ -1314,6 +1319,28 @@ class DatabaseMigration {
       logger.warn(`Failed to migrate cpfp transaction data`);
     }
   }
+
+  private async $fixBadV1AuditBlocks(): Promise<void> {
+    const badBlocks = [
+      '000000000000000000011ad49227fc8c9ba0ca96ad2ebce41a862f9a244478dc',
+      '000000000000000000010ac1f68b3080153f2826ffddc87ceffdd68ed97d6960',
+      '000000000000000000024cbdafeb2660ae8bd2947d166e7fe15d1689e86b2cf7',
+      '00000000000000000002e1dbfbf6ae057f331992a058b822644b368034f87286',
+      '0000000000000000000019973b2778f08ad6d21e083302ff0833d17066921ebb',
+    ];
+
+    for (const hash of badBlocks) {
+      try {
+        await this.$executeQuery(`
+          UPDATE blocks_audits
+          SET prioritized_txs = '[]'
+          WHERE hash = '${hash}'
+        `, true);
+      } catch (e) {
+        continue;
+      }
+    }
+  }
 }
 
 export default new DatabaseMigration();
diff --git a/backend/src/api/disk-cache.ts b/backend/src/api/disk-cache.ts
index 202f8f4cb..f2a1f2390 100644
--- a/backend/src/api/disk-cache.ts
+++ b/backend/src/api/disk-cache.ts
@@ -257,6 +257,7 @@ class DiskCache {
           trees: rbfData.rbf.trees,
           expiring: rbfData.rbf.expiring.map(([txid, value]) => ({ key: txid, value })),
           mempool: memPool.getMempool(),
+          spendMap: memPool.getSpendMap(),
         });
       }
     } catch (e) {
diff --git a/backend/src/api/explorer/channels.routes.ts b/backend/src/api/explorer/channels.routes.ts
index 391bf628e..8b4c3e8c8 100644
--- a/backend/src/api/explorer/channels.routes.ts
+++ b/backend/src/api/explorer/channels.routes.ts
@@ -1,6 +1,7 @@
 import config from '../../config';
 import { Application, Request, Response } from 'express';
 import channelsApi from './channels.api';
+import { handleError } from '../../utils/api';
 
 class ChannelsRoutes {
   constructor() { }
@@ -22,7 +23,7 @@ class ChannelsRoutes {
       const channels = await channelsApi.$searchChannelsById(req.params.search);
       res.json(channels);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -38,7 +39,7 @@ class ChannelsRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(channel);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -53,11 +54,11 @@ class ChannelsRoutes {
       const status: string = typeof req.query.status === 'string' ? req.query.status : '';
 
       if (index < -1) {
-        res.status(400).send('Invalid index');
+        handleError(req, res, 400, 'Invalid index');
         return;
       }
       if (['open', 'active', 'closed'].includes(status) === false) {
-        res.status(400).send('Invalid status');
+        handleError(req, res, 400, 'Invalid status');
         return;
       }
 
@@ -69,14 +70,14 @@ class ChannelsRoutes {
       res.header('X-Total-Count', channelsCount.toString());
       res.json(channels);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
   private async $getChannelsByTransactionIds(req: Request, res: Response): Promise<void> {
     try {
       if (!Array.isArray(req.query.txId)) {
-        res.status(400).send('Not an array');
+        handleError(req, res, 400, 'Not an array');
         return;
       }
       const txIds: string[] = [];
@@ -107,7 +108,7 @@ class ChannelsRoutes {
 
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -119,7 +120,7 @@ class ChannelsRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(channels);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -132,7 +133,7 @@ class ChannelsRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(channels);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
diff --git a/backend/src/api/explorer/general.routes.ts b/backend/src/api/explorer/general.routes.ts
index 07620e84a..b4d0c635d 100644
--- a/backend/src/api/explorer/general.routes.ts
+++ b/backend/src/api/explorer/general.routes.ts
@@ -3,6 +3,8 @@ import { Application, Request, Response } from 'express';
 import nodesApi from './nodes.api';
 import channelsApi from './channels.api';
 import statisticsApi from './statistics.api';
+import { handleError } from '../../utils/api';
+
 class GeneralLightningRoutes {
   constructor() { }
 
@@ -27,7 +29,7 @@ class GeneralLightningRoutes {
         channels: channels,
       });
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -41,7 +43,7 @@ class GeneralLightningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(statistics);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -50,7 +52,7 @@ class GeneralLightningRoutes {
       const statistics = await statisticsApi.$getLatestStatistics();
       res.json(statistics);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 }
diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts
index 9d6373845..9ca2fd1c3 100644
--- a/backend/src/api/explorer/nodes.routes.ts
+++ b/backend/src/api/explorer/nodes.routes.ts
@@ -3,6 +3,7 @@ import { Application, Request, Response } from 'express';
 import nodesApi from './nodes.api';
 import DB from '../../database';
 import { INodesRanking } from '../../mempool.interfaces';
+import { handleError } from '../../utils/api';
 
 class NodesRoutes {
   constructor() { }
@@ -31,7 +32,7 @@ class NodesRoutes {
       const nodes = await nodesApi.$searchNodeByPublicKeyOrAlias(req.params.search);
       res.json(nodes);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -181,13 +182,13 @@ class NodesRoutes {
           }
         } catch (e) {}
       }
-      
+
       res.header('Pragma', 'public');
       res.header('Cache-control', 'public');
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(nodes);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -195,7 +196,7 @@ class NodesRoutes {
     try {
       const node = await nodesApi.$getNode(req.params.public_key);
       if (!node) {
-        res.status(404).send('Node not found');
+        handleError(req, res, 404, 'Node not found');
         return;
       }
       res.header('Pragma', 'public');
@@ -203,7 +204,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(node);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -215,7 +216,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(statistics);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -223,7 +224,7 @@ class NodesRoutes {
     try {
       const node = await nodesApi.$getFeeHistogram(req.params.public_key);
       if (!node) {
-        res.status(404).send('Node not found');
+        handleError(req, res, 404, 'Node not found');
         return;
       }
       res.header('Pragma', 'public');
@@ -231,7 +232,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(node);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -247,7 +248,7 @@ class NodesRoutes {
         topByChannels: topChannelsNodes,
       });
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -259,7 +260,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(topCapacityNodes);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -271,7 +272,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(topCapacityNodes);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -283,7 +284,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(topCapacityNodes);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -295,7 +296,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
       res.json(nodesPerAs);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -307,7 +308,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
       res.json(worldNodes);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -322,7 +323,7 @@ class NodesRoutes {
       );
 
       if (country.length === 0) {
-        res.status(404).send(`This country does not exist or does not host any lightning nodes on clearnet`);
+        handleError(req, res, 404, `This country does not exist or does not host any lightning nodes on clearnet`);
         return;
       }
 
@@ -335,7 +336,7 @@ class NodesRoutes {
         nodes: nodes,
       });
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -349,7 +350,7 @@ class NodesRoutes {
       );
 
       if (isp.length === 0) {
-        res.status(404).send(`This ISP does not exist or does not host any lightning nodes on clearnet`);
+        handleError(req, res, 404, `This ISP does not exist or does not host any lightning nodes on clearnet`);
         return;
       }
 
@@ -362,7 +363,7 @@ class NodesRoutes {
         nodes: nodes,
       });
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -374,7 +375,7 @@ class NodesRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
       res.json(nodesPerAs);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 }
diff --git a/backend/src/api/liquid/liquid.routes.ts b/backend/src/api/liquid/liquid.routes.ts
index 9ea61ca31..9dafd0def 100644
--- a/backend/src/api/liquid/liquid.routes.ts
+++ b/backend/src/api/liquid/liquid.routes.ts
@@ -3,6 +3,7 @@ import { Application, Request, Response } from 'express';
 import config from '../../config';
 import elementsParser from './elements-parser';
 import icons from './icons';
+import { handleError } from '../../utils/api';
 
 class LiquidRoutes {
   public initRoutes(app: Application) {
@@ -42,7 +43,7 @@ class LiquidRoutes {
       res.setHeader('content-length', result.length);
       res.send(result);
     } else {
-      res.status(404).send('Asset icon not found');
+      handleError(req, res, 404, 'Asset icon not found');
     }
   }
 
@@ -51,7 +52,7 @@ class LiquidRoutes {
     if (result) {
       res.json(result);
     } else {
-      res.status(404).send('Asset icons not found');
+      handleError(req, res, 404, 'Asset icons not found');
     }
   }
 
@@ -82,7 +83,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60 * 60).toUTCString());
       res.json(pegs);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -94,7 +95,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60 * 60).toUTCString());
       res.json(reserves);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -106,7 +107,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(currentSupply);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -118,7 +119,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(currentReserves);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -130,7 +131,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(auditStatus);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -142,7 +143,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(federationAddresses);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -154,7 +155,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(federationAddresses);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -166,7 +167,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(federationUtxos);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -178,7 +179,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(expiredUtxos);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -190,7 +191,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(federationUtxos);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -202,7 +203,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(emergencySpentUtxos);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -214,7 +215,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(emergencySpentUtxos);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -226,7 +227,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(recentPegs);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -238,7 +239,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(pegsVolume);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -250,7 +251,7 @@ class LiquidRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
       res.json(pegsCount);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts
index 5d9dcf8f4..6e547e653 100644
--- a/backend/src/api/mempool-blocks.ts
+++ b/backend/src/api/mempool-blocks.ts
@@ -369,7 +369,7 @@ class MempoolBlocks {
     const lastBlockIndex = blocks.length - 1;
     let hasBlockStack = blocks.length >= 8;
     let stackWeight;
-    let feeStatsCalculator: OnlineFeeStatsCalculator | void;
+    let feeStatsCalculator: OnlineFeeStatsCalculator | null = null;
     if (hasBlockStack) {
       if (blockWeights && blockWeights[7] !== null) {
         stackWeight = blockWeights[7];
@@ -380,28 +380,36 @@ class MempoolBlocks {
       feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5, [10, 20, 30, 40, 50, 60, 70, 80, 90]);
     }
 
+    const ancestors: Ancestor[] = [];
+    const descendants: Ancestor[] = [];
+    let ancestor: MempoolTransactionExtended
     for (const cluster of clusters) {
       for (const memberTxid of cluster) {
         const mempoolTx = mempool[memberTxid];
         if (mempoolTx) {
-          const ancestors: Ancestor[] = [];
-          const descendants: Ancestor[] = [];
+          // ugly micro-optimization to avoid allocating new arrays
+          ancestors.length = 0;
+          descendants.length = 0;
           let matched = false;
           cluster.forEach(txid => {
+            ancestor = mempool[txid];
             if (txid === memberTxid) {
               matched = true;
             } else {
-              if (!mempool[txid]) {
+              if (!ancestor) {
                 console.log('txid missing from mempool! ', txid, candidates?.txs[txid]);
+                return;
               }
               const relative = {
                 txid: txid,
-                fee: mempool[txid].fee,
-                weight: (mempool[txid].adjustedVsize * 4),
+                fee: ancestor.fee,
+                weight: (ancestor.adjustedVsize * 4),
               };
               if (matched) {
                 descendants.push(relative);
-                mempoolTx.lastBoosted = Math.max(mempoolTx.lastBoosted || 0, mempool[txid].firstSeen || 0);
+                if (!mempoolTx.lastBoosted || (ancestor.firstSeen && ancestor.firstSeen > mempoolTx.lastBoosted)) {
+                  mempoolTx.lastBoosted = ancestor.firstSeen;
+                }
               } else {
                 ancestors.push(relative);
               }
@@ -410,7 +418,20 @@ class MempoolBlocks {
           if (mempoolTx.ancestors?.length !== ancestors.length || mempoolTx.descendants?.length !== descendants.length) {
             mempoolTx.cpfpDirty = true;
           }
-          Object.assign(mempoolTx, {ancestors, descendants, bestDescendant: null, cpfpChecked: true});
+          // ugly micro-optimization to avoid allocating new arrays or objects
+          if (mempoolTx.ancestors) {
+            mempoolTx.ancestors.length = 0;
+          } else {
+            mempoolTx.ancestors = [];
+          }
+          if (mempoolTx.descendants) {
+            mempoolTx.descendants.length = 0;
+          } else {
+            mempoolTx.descendants = [];
+          }
+          mempoolTx.ancestors.push(...ancestors);
+          mempoolTx.descendants.push(...descendants);
+          mempoolTx.cpfpChecked = true;
         }
       }
     }
@@ -420,7 +441,10 @@ class MempoolBlocks {
     const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
     // update this thread's mempool with the results
     let mempoolTx: MempoolTransactionExtended;
-    const mempoolBlocks: MempoolBlockWithTransactions[] = blocks.map((block, blockIndex) => {
+    let acceleration: Acceleration;
+    const mempoolBlocks: MempoolBlockWithTransactions[] = [];
+    for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
+      const block = blocks[blockIndex];
       let totalSize = 0;
       let totalVsize = 0;
       let totalWeight = 0;
@@ -436,7 +460,8 @@ class MempoolBlocks {
         }
       }
 
-      for (const txid of block) {
+      for (let i = 0; i < block.length; i++) {
+        const txid = block[i];
         if (txid) {
           mempoolTx = mempool[txid];
           // save position in projected blocks
@@ -445,30 +470,37 @@ class MempoolBlocks {
             vsize: totalVsize + (mempoolTx.vsize / 2),
           };
 
-          const acceleration = accelerations[txid];
-          if (isAcceleratedBy[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) {
-            if (!mempoolTx.acceleration) {
-              mempoolTx.cpfpDirty = true;
-            }
-            mempoolTx.acceleration = true;
-            mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools;
-            mempoolTx.acceleratedAt = acceleration?.added;
-            mempoolTx.feeDelta = acceleration?.feeDelta;
-            for (const ancestor of mempoolTx.ancestors || []) {
-              if (!mempool[ancestor.txid].acceleration) {
-                mempool[ancestor.txid].cpfpDirty = true;
+          if (txid in accelerations) {
+            acceleration = accelerations[txid];
+            if (isAcceleratedBy[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) {
+              if (!mempoolTx.acceleration) {
+                mempoolTx.cpfpDirty = true;
+              }
+              mempoolTx.acceleration = true;
+              mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools;
+              mempoolTx.acceleratedAt = acceleration?.added;
+              mempoolTx.feeDelta = acceleration?.feeDelta;
+              for (const ancestor of mempoolTx.ancestors || []) {
+                if (!mempool[ancestor.txid].acceleration) {
+                  mempool[ancestor.txid].cpfpDirty = true;
+                }
+                mempool[ancestor.txid].acceleration = true;
+                mempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy;
+                mempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt;
+                mempool[ancestor.txid].feeDelta = mempoolTx.feeDelta;
+                isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy;
+              }
+            } else {
+              if (mempoolTx.acceleration) {
+                mempoolTx.cpfpDirty = true;
+                delete mempoolTx.acceleration;
               }
-              mempool[ancestor.txid].acceleration = true;
-              mempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy;
-              mempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt;
-              mempool[ancestor.txid].feeDelta = mempoolTx.feeDelta;
-              isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy;
             }
           } else {
             if (mempoolTx.acceleration) {
               mempoolTx.cpfpDirty = true;
+              delete mempoolTx.acceleration;
             }
-            delete mempoolTx.acceleration;
           }
 
           // online calculation of stack-of-blocks fee stats
@@ -486,7 +518,7 @@ class MempoolBlocks {
           }
         }
       }
-      return this.dataToMempoolBlocks(
+      mempoolBlocks[blockIndex] = this.dataToMempoolBlocks(
         block,
         transactions,
         totalSize,
@@ -494,7 +526,7 @@ class MempoolBlocks {
         totalFees,
         (hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined,
       );
-    });
+    };
 
     if (saveResults) {
       const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts
index 1f55179fb..1442b05fa 100644
--- a/backend/src/api/mempool.ts
+++ b/backend/src/api/mempool.ts
@@ -19,12 +19,13 @@ class Mempool {
   private mempoolCache: { [txId: string]: MempoolTransactionExtended } = {};
   private mempoolCandidates: { [txid: string ]: boolean } = {};
   private spendMap = new Map<string, MempoolTransactionExtended>();
+  private recentlyDeleted: MempoolTransactionExtended[][] = []; // buffer of transactions deleted in recent mempool updates
   private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
                                                     maxmempool: 300000000, mempoolminfee: Common.isLiquid() ? 0.00000100 : 0.00001000, minrelaytxfee: Common.isLiquid() ? 0.00000100 : 0.00001000 };
   private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
-    deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => void) | undefined;
+    deletedTransactions: MempoolTransactionExtended[][], accelerationDelta: string[]) => void) | undefined;
   private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, mempoolSize: number, newTransactions: MempoolTransactionExtended[],
-    deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[], candidates?: GbtCandidates) => Promise<void>) | undefined;
+    deletedTransactions: MempoolTransactionExtended[][], accelerationDelta: string[], candidates?: GbtCandidates) => Promise<void>) | undefined;
 
   private accelerations: { [txId: string]: Acceleration } = {};
   private accelerationPositions: { [txid: string]: { poolId: number, pool: string, block: number, vsize: number }[] } = {};
@@ -74,12 +75,12 @@ class Mempool {
   }
 
   public setMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
-    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => void): void {
+    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[][], accelerationDelta: string[]) => void): void {
     this.mempoolChangedCallback = fn;
   }
 
   public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; }, mempoolSize: number,
-    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[],
+    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[][], accelerationDelta: string[],
     candidates?: GbtCandidates) => Promise<void>): void {
     this.$asyncMempoolChangedCallback = fn;
   }
@@ -362,12 +363,15 @@ class Mempool {
 
     const candidatesChanged = candidates?.added?.length || candidates?.removed?.length;
 
-    if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
-      this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions, accelerationDelta);
+    this.recentlyDeleted.unshift(deletedTransactions);
+    this.recentlyDeleted.length = Math.min(this.recentlyDeleted.length, 10); // truncate to the last 10 mempool updates
+
+    if (this.mempoolChangedCallback && (hasChange || newTransactions.length || deletedTransactions.length)) {
+      this.mempoolChangedCallback(this.mempoolCache, newTransactions, this.recentlyDeleted, accelerationDelta);
     }
-    if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length || candidatesChanged)) {
+    if (this.$asyncMempoolChangedCallback && (hasChange || newTransactions.length || deletedTransactions.length || candidatesChanged)) {
       this.updateTimerProgress(timer, 'running async mempool callback');
-      await this.$asyncMempoolChangedCallback(this.mempoolCache, newMempoolSize, newTransactions, deletedTransactions, accelerationDelta, candidates);
+      await this.$asyncMempoolChangedCallback(this.mempoolCache, newMempoolSize, newTransactions, this.recentlyDeleted, accelerationDelta, candidates);
       this.updateTimerProgress(timer, 'completed async mempool callback');
     }
 
@@ -541,16 +545,7 @@ class Mempool {
     }
   }
 
-  public handleRbfTransactions(rbfTransactions: { [txid: string]: MempoolTransactionExtended[]; }): void {
-    for (const rbfTransaction in rbfTransactions) {
-      if (this.mempoolCache[rbfTransaction] && rbfTransactions[rbfTransaction]?.length) {
-        // Store replaced transactions
-        rbfCache.add(rbfTransactions[rbfTransaction], this.mempoolCache[rbfTransaction]);
-      }
-    }
-  }
-
-  public handleMinedRbfTransactions(rbfTransactions: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }}): void {
+  public handleRbfTransactions(rbfTransactions: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }}): void {
     for (const rbfTransaction in rbfTransactions) {
       if (rbfTransactions[rbfTransaction].replacedBy && rbfTransactions[rbfTransaction]?.replaced?.length) {
         // Store replaced transactions
diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts
index 8f8bbac82..69e6d95d4 100644
--- a/backend/src/api/mining/mining-routes.ts
+++ b/backend/src/api/mining/mining-routes.ts
@@ -10,6 +10,7 @@ import mining from "./mining";
 import PricesRepository from '../../repositories/PricesRepository';
 import AccelerationRepository from '../../repositories/AccelerationRepository';
 import accelerationApi from '../services/acceleration';
+import { handleError } from '../../utils/api';
 
 class MiningRoutes {
   public initRoutes(app: Application) {
@@ -53,12 +54,12 @@ class MiningRoutes {
       res.header('Cache-control', 'public');
       res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
       if (['testnet', 'signet', 'liquidtestnet'].includes(config.MEMPOOL.NETWORK)) {
-        res.status(400).send('Prices are not available on testnets.');
+        handleError(req, res, 400, 'Prices are not available on testnets.');
         return;
       }
       const timestamp = parseInt(req.query.timestamp as string, 10) || 0;
       const currency = req.query.currency as string;
-      
+
       let response;
       if (timestamp && currency) {
         response = await PricesRepository.$getNearestHistoricalPrice(timestamp, currency);
@@ -71,7 +72,7 @@ class MiningRoutes {
       }
       res.status(200).send(response);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -84,9 +85,9 @@ class MiningRoutes {
       res.json(stats);
     } catch (e) {
       if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
-        res.status(404).send(e.message);
+        handleError(req, res, 404, e.message);
       } else {
-        res.status(500).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 500, e instanceof Error ? e.message : e);
       }
     }
   }
@@ -103,9 +104,9 @@ class MiningRoutes {
       res.json(poolBlocks);
     } catch (e) {
       if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
-        res.status(404).send(e.message);
+        handleError(req, res, 404, e.message);
       } else {
-        res.status(500).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 500, e instanceof Error ? e.message : e);
       }
     }
   }
@@ -129,7 +130,7 @@ class MiningRoutes {
         res.json(pools);
       }
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -143,7 +144,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(stats);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -157,7 +158,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
       res.json(hashrates);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -172,9 +173,9 @@ class MiningRoutes {
       res.json(hashrates);
     } catch (e) {
       if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
-        res.status(404).send(e.message);
+        handleError(req, res, 404, e.message);
       } else {
-        res.status(500).send(e instanceof Error ? e.message : e);
+        handleError(req, res, 500, e instanceof Error ? e.message : e);
       }
     }
   }
@@ -203,7 +204,7 @@ class MiningRoutes {
         currentDifficulty: currentDifficulty,
       });
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -217,7 +218,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(blockFees);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -235,7 +236,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(blockFees);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -249,7 +250,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(blockRewards);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -263,7 +264,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(blockFeeRates);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -281,7 +282,7 @@ class MiningRoutes {
         weights: blockWeights
       });
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -293,7 +294,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
       res.json(difficulty.map(adj => [adj.time, adj.height, adj.difficulty, adj.adjustment]));
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -317,7 +318,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(blocksHealth.map(health => [health.time, health.height, health.match_rate]));
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -326,7 +327,7 @@ class MiningRoutes {
       const audit = await BlocksAuditsRepository.$getBlockAudit(req.params.hash);
 
       if (!audit) {
-        res.status(204).send(`This block has not been audited.`);
+        handleError(req, res, 204, `This block has not been audited.`);
         return;
       }
 
@@ -335,7 +336,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString());
       res.json(audit);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -358,7 +359,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
       res.json(result);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -371,7 +372,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       res.json(await BlocksAuditsRepository.$getBlockAuditScores(height, height - 15));
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -384,7 +385,7 @@ class MiningRoutes {
       res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString());
       res.json(audit || 'null');
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -394,12 +395,12 @@ class MiningRoutes {
       res.header('Cache-control', 'public');
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
-        res.status(400).send('Acceleration data is not available.');
+        handleError(req, res, 400, 'Acceleration data is not available.');
         return;
       }
       res.status(200).send(await AccelerationRepository.$getAccelerationInfo(req.params.slug));
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -409,13 +410,13 @@ class MiningRoutes {
       res.header('Cache-control', 'public');
       res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString());
       if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
-        res.status(400).send('Acceleration data is not available.');
+        handleError(req, res, 400, 'Acceleration data is not available.');
         return;
       }
       const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
       res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, height));
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -425,12 +426,12 @@ class MiningRoutes {
       res.header('Cache-control', 'public');
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
-        res.status(400).send('Acceleration data is not available.');
+        handleError(req, res, 400, 'Acceleration data is not available.');
         return;
       }
       res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, null, req.params.interval));
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -440,12 +441,12 @@ class MiningRoutes {
       res.header('Cache-control', 'public');
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
-        res.status(400).send('Acceleration data is not available.');
+        handleError(req, res, 400, 'Acceleration data is not available.');
         return;
       }
       res.status(200).send(await AccelerationRepository.$getAccelerationTotals(<string>req.query.pool, <string>req.query.interval));
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -455,12 +456,12 @@ class MiningRoutes {
       res.header('Cache-control', 'public');
       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
       if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
-        res.status(400).send('Acceleration data is not available.');
+        handleError(req, res, 400, 'Acceleration data is not available.');
         return;
       }
       res.status(200).send(accelerationApi.accelerations || []);
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 
@@ -472,7 +473,7 @@ class MiningRoutes {
       accelerationApi.accelerationRequested(req.params.txid);
       res.status(200).send();
     } catch (e) {
-      res.status(500).send(e instanceof Error ? e.message : e);
+      handleError(req, res, 500, e instanceof Error ? e.message : e);
     }
   }
 }
diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts
index a087abbe0..944ad790e 100644
--- a/backend/src/api/rbf-cache.ts
+++ b/backend/src/api/rbf-cache.ts
@@ -44,6 +44,22 @@ interface CacheEvent {
   value?: any,
 }
 
+/**
+ * Singleton for tracking RBF trees
+ *
+ * Maintains a set of RBF trees, where each tree represents a sequence of
+ * consecutive RBF replacements.
+ *
+ * Trees are identified by the txid of the root transaction.
+ *
+ * To maintain consistency, the following invariants must be upheld:
+ *  - Symmetry: replacedBy(A) = B <=> A in replaces(B)
+ *  - Unique id: treeMap(treeMap(X)) = treeMap(X)
+ *  - Unique tree: A in replaces(B) => treeMap(A) == treeMap(B)
+ *  - Existence: X in treeMap => treeMap(X) in rbfTrees
+ *  - Completeness: X in replacedBy => X in treeMap, Y in replaces => Y in treeMap
+ */
+
 class RbfCache {
   private replacedBy: Map<string, string> = new Map();
   private replaces: Map<string, string[]> = new Map();
@@ -61,6 +77,10 @@ class RbfCache {
     setInterval(this.cleanup.bind(this), 1000 * 60 * 10);
   }
 
+  /**
+   * Low level cache operations
+   */
+
   private addTx(txid: string, tx: MempoolTransactionExtended): void {
     this.txs.set(txid, tx);
     this.cacheQueue.push({ op: CacheOp.Add, type: 'tx', txid });
@@ -92,6 +112,12 @@ class RbfCache {
     this.cacheQueue.push({ op: CacheOp.Remove, type: 'exp', txid });
   }
 
+  /**
+   * Basic data structure operations
+   * must uphold tree invariants
+   */
+
+
   public add(replaced: MempoolTransactionExtended[], newTxExtended: MempoolTransactionExtended): void {
     if (!newTxExtended || !replaced?.length || this.txs.has(newTxExtended.txid)) {
       return;
@@ -114,6 +140,10 @@ class RbfCache {
       if (!replacedTx.rbf) {
         txFullRbf = true;
       }
+      if (this.replacedBy.has(replacedTx.txid)) {
+        // should never happen
+        continue;
+      }
       this.replacedBy.set(replacedTx.txid, newTx.txid);
       if (this.treeMap.has(replacedTx.txid)) {
         const treeId = this.treeMap.get(replacedTx.txid);
@@ -140,18 +170,47 @@ class RbfCache {
       }
     }
     newTx.fullRbf = txFullRbf;
-    const treeId = replacedTrees[0].tx.txid;
     const newTree = {
       tx: newTx,
       time: newTime,
       fullRbf: treeFullRbf,
       replaces: replacedTrees
     };
-    this.addTree(treeId, newTree);
-    this.updateTreeMap(treeId, newTree);
+    this.addTree(newTree.tx.txid, newTree);
+    this.updateTreeMap(newTree.tx.txid, newTree);
     this.replaces.set(newTx.txid, replacedTrees.map(tree => tree.tx.txid));
   }
 
+  public mined(txid): void {
+    if (!this.txs.has(txid)) {
+      return;
+    }
+    const treeId = this.treeMap.get(txid);
+    if (treeId && this.rbfTrees.has(treeId)) {
+      const tree = this.rbfTrees.get(treeId);
+      if (tree) {
+        this.setTreeMined(tree, txid);
+        tree.mined = true;
+        this.dirtyTrees.add(treeId);
+        this.cacheQueue.push({ op: CacheOp.Change, type: 'tree', txid: treeId });
+      }
+    }
+    this.evict(txid);
+  }
+
+  // flag a transaction as removed from the mempool
+  public evict(txid: string, fast: boolean = false): void {
+    this.evictionCount++;
+    if (this.txs.has(txid) && (fast || !this.expiring.has(txid))) {
+      const expiryTime = fast ? Date.now() + (1000 * 60 * 10) : Date.now() + (1000 * 86400); // 24 hours
+      this.addExpiration(txid, expiryTime);
+    }
+  }
+
+  /**
+   * Read-only public interface
+   */
+
   public has(txId: string): boolean {
     return this.txs.has(txId);
   }
@@ -232,32 +291,6 @@ class RbfCache {
     return changes;
   }
 
-  public mined(txid): void {
-    if (!this.txs.has(txid)) {
-      return;
-    }
-    const treeId = this.treeMap.get(txid);
-    if (treeId && this.rbfTrees.has(treeId)) {
-      const tree = this.rbfTrees.get(treeId);
-      if (tree) {
-        this.setTreeMined(tree, txid);
-        tree.mined = true;
-        this.dirtyTrees.add(treeId);
-        this.cacheQueue.push({ op: CacheOp.Change, type: 'tree', txid: treeId });
-      }
-    }
-    this.evict(txid);
-  }
-
-  // flag a transaction as removed from the mempool
-  public evict(txid: string, fast: boolean = false): void {
-    this.evictionCount++;
-    if (this.txs.has(txid) && (fast || !this.expiring.has(txid))) {
-      const expiryTime = fast ? Date.now() + (1000 * 60 * 10) : Date.now() + (1000 * 86400); // 24 hours
-      this.addExpiration(txid, expiryTime);
-    }
-  }
-
   // is the transaction involved in a full rbf replacement?
   public isFullRbf(txid: string): boolean {
     const treeId = this.treeMap.get(txid);
@@ -271,6 +304,10 @@ class RbfCache {
     return tree?.fullRbf;
   }
 
+  /**
+   * Cache maintenance & utility functions
+   */
+
   private cleanup(): void {
     const now = Date.now();
     for (const txid of this.expiring.keys()) {
@@ -299,10 +336,6 @@ class RbfCache {
       for (const tx of (replaces || [])) {
         // recursively remove prior versions from the cache
         this.replacedBy.delete(tx);
-        // if this is the id of a tree, remove that too
-        if (this.treeMap.get(tx) === tx) {
-          this.removeTree(tx);
-        }
         this.remove(tx);
       }
     }
@@ -370,14 +403,21 @@ class RbfCache {
     };
   }
 
-  public async load({ txs, trees, expiring, mempool }): Promise<void> {
+  public async load({ txs, trees, expiring, mempool, spendMap }): Promise<void> {
     try {
       txs.forEach(txEntry => {
         this.txs.set(txEntry.value.txid, txEntry.value);
       });
       this.staleCount = 0;
-      for (const deflatedTree of trees) {
-        await this.importTree(mempool, deflatedTree.root, deflatedTree.root, deflatedTree, this.txs);
+      for (const deflatedTree of trees.sort((a, b) => Object.keys(b).length - Object.keys(a).length)) {
+        const tree = await this.importTree(mempool, deflatedTree.root, deflatedTree.root, deflatedTree, this.txs);
+        if (tree) {
+          this.addTree(tree.tx.txid, tree);
+          this.updateTreeMap(tree.tx.txid, tree);
+          if (tree.mined) {
+            this.evict(tree.tx.txid);
+          }
+        }
       }
       expiring.forEach(expiringEntry => {
         if (this.txs.has(expiringEntry.key)) {
@@ -385,6 +425,31 @@ class RbfCache {
         }
       });
       this.staleCount = 0;
+
+      // connect cached trees to current mempool transactions
+      const conflicts: Record<string, { replacedBy: MempoolTransactionExtended, replaces: Set<MempoolTransactionExtended> }> = {};
+      for (const tree of this.rbfTrees.values()) {
+        const tx = this.getTx(tree.tx.txid);
+        if (!tx || tree.mined) {
+          continue;
+        }
+        for (const vin of tx.vin) {
+          const conflict = spendMap.get(`${vin.txid}:${vin.vout}`);
+          if (conflict && conflict.txid !== tx.txid) {
+            if (!conflicts[conflict.txid]) {
+              conflicts[conflict.txid] = {
+                replacedBy: conflict,
+                replaces: new Set(),
+              };
+            }
+            conflicts[conflict.txid].replaces.add(tx);
+          }
+        }
+      }
+      for (const { replacedBy, replaces } of Object.values(conflicts)) {
+        this.add([...replaces.values()], replacedBy);
+      }
+
       await this.checkTrees();
       logger.debug(`loaded ${txs.length} txs, ${trees.length} trees into rbf cache, ${expiring.length} due to expire, ${this.staleCount} were stale`);
       this.cleanup();
@@ -426,6 +491,12 @@ class RbfCache {
       return;
     }
 
+    // if this tx is already in the cache, return early
+    if (this.treeMap.has(txid)) {
+      this.removeTree(deflated.key);
+      return;
+    }
+
     // recursively reconstruct child trees
     for (const childId of treeInfo.replaces) {
       const replaced = await this.importTree(mempool, root, childId, deflated, txs, mined);
@@ -457,10 +528,6 @@ class RbfCache {
       fullRbf: treeInfo.fullRbf,
       replaces,
     };
-    this.treeMap.set(txid, root);
-    if (root === txid) {
-      this.addTree(root, tree);
-    }
     return tree;
   }
 
@@ -511,6 +578,7 @@ class RbfCache {
       processTxs(txs);
     }
 
+    // evict missing transactions
     for (const txid of txids) {
       if (!found[txid]) {
         this.evict(txid, false);
diff --git a/backend/src/api/redis-cache.ts b/backend/src/api/redis-cache.ts
index cbfa2f18b..1caade15b 100644
--- a/backend/src/api/redis-cache.ts
+++ b/backend/src/api/redis-cache.ts
@@ -365,6 +365,7 @@ class RedisCache {
       trees: rbfTrees.map(loadedTree => { loadedTree.value.key = loadedTree.key; return loadedTree.value; }),
       expiring: rbfExpirations,
       mempool: memPool.getMempool(),
+      spendMap: memPool.getSpendMap(),
     });
   }
 
diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts
index b3077b935..28fa72bba 100644
--- a/backend/src/api/transaction-utils.ts
+++ b/backend/src/api/transaction-utils.ts
@@ -121,6 +121,7 @@ class TransactionUtils {
     const adjustedVsize = Math.max(fractionalVsize, sigops *  5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor
     const feePerVbytes = (transaction.fee || 0) / fractionalVsize;
     const adjustedFeePerVsize = (transaction.fee || 0) / adjustedVsize;
+    const effectiveFeePerVsize = transaction['effectiveFeePerVsize'] || adjustedFeePerVsize || feePerVbytes;
     const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, {
       order: this.txidToOrdering(transaction.txid),
       vsize,
@@ -128,7 +129,7 @@ class TransactionUtils {
       sigops,
       feePerVsize: feePerVbytes,
       adjustedFeePerVsize: adjustedFeePerVsize,
-      effectiveFeePerVsize: adjustedFeePerVsize,
+      effectiveFeePerVsize: effectiveFeePerVsize,
     });
     if (!transactionExtended?.status?.confirmed && !transactionExtended.firstSeen) {
       transactionExtended.firstSeen = Math.round((Date.now() / 1000));
@@ -338,6 +339,87 @@ class TransactionUtils {
     const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2;
     return witness[positionOfScript];
   }
+
+  // calculate the most parsimonious set of prioritizations given a list of block transactions
+  // (i.e. the most likely prioritizations and deprioritizations)
+  public identifyPrioritizedTransactions(transactions: any[], rateKey: string): { prioritized: string[], deprioritized: string[] } {
+    // find the longest increasing subsequence of transactions
+    // (adapted from https://en.wikipedia.org/wiki/Longest_increasing_subsequence#Efficient_algorithms)
+    // should be O(n log n)
+    const X = transactions.slice(1).reverse().map((tx) => ({ txid: tx.txid, rate: tx[rateKey] })); // standard block order is by *decreasing* effective fee rate, but we want to iterate in increasing order (and skip the coinbase)
+    if (X.length < 2) {
+      return { prioritized: [], deprioritized: [] };
+    }
+    const N = X.length;
+    const P: number[] = new Array(N);
+    const M: number[] = new Array(N + 1);
+    M[0] = -1; // undefined so can be set to any value
+
+    let L = 0;
+    for (let i = 0; i < N; i++) {
+      // Binary search for the smallest positive l ≤ L
+      // such that X[M[l]].effectiveFeePerVsize > X[i].effectiveFeePerVsize
+      let lo = 1;
+      let hi = L + 1;
+      while (lo < hi) {
+        const mid = lo + Math.floor((hi - lo) / 2); // lo <= mid < hi
+        if (X[M[mid]].rate > X[i].rate) {
+          hi = mid;
+        } else { // if X[M[mid]].effectiveFeePerVsize < X[i].effectiveFeePerVsize
+          lo = mid + 1;
+        }
+      }
+
+      // After searching, lo == hi is 1 greater than the
+      // length of the longest prefix of X[i]
+      const newL = lo;
+
+      // The predecessor of X[i] is the last index of
+      // the subsequence of length newL-1
+      P[i] = M[newL - 1];
+      M[newL] = i;
+
+      if (newL > L) {
+        // If we found a subsequence longer than any we've
+        // found yet, update L
+        L = newL;
+      }
+    }
+
+    // Reconstruct the longest increasing subsequence
+    // It consists of the values of X at the L indices:
+    // ..., P[P[M[L]]], P[M[L]], M[L]
+    const LIS: any[] = new Array(L);
+    let k = M[L];
+    for (let j = L - 1; j >= 0; j--) {
+      LIS[j] = X[k];
+      k = P[k];
+    }
+
+    const lisMap = new Map<string, number>();
+    LIS.forEach((tx, index) => lisMap.set(tx.txid, index));
+
+    const prioritized: string[] = [];
+    const deprioritized: string[] = [];
+
+    let lastRate = X[0].rate;
+
+    for (const tx of X) {
+      if (lisMap.has(tx.txid)) {
+        lastRate = tx.rate;
+      } else {
+        if (Math.abs(tx.rate - lastRate) < 0.1) {
+          // skip if the rate is almost the same as the previous transaction
+        } else if (tx.rate <= lastRate) {
+          prioritized.push(tx.txid);
+        } else {
+          deprioritized.push(tx.txid);
+        }
+      }
+    }
+
+    return { prioritized, deprioritized };
+  }
 }
 
 export default new TransactionUtils();
diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts
index 79a783f88..2a047472e 100644
--- a/backend/src/api/websocket-handler.ts
+++ b/backend/src/api/websocket-handler.ts
@@ -520,8 +520,17 @@ class WebsocketHandler {
     }
   }
 
+  /**
+   *
+   * @param newMempool
+   * @param mempoolSize
+   * @param newTransactions  array of transactions added this mempool update.
+   * @param recentlyDeletedTransactions array of arrays of transactions removed in the last N mempool updates, most recent first.
+   * @param accelerationDelta
+   * @param candidates
+   */
   async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number,
-    newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[],
+    newTransactions: MempoolTransactionExtended[], recentlyDeletedTransactions: MempoolTransactionExtended[][], accelerationDelta: string[],
     candidates?: GbtCandidates): Promise<void> {
     if (!this.webSocketServers.length) {
       throw new Error('No WebSocket.Server have been set');
@@ -529,6 +538,8 @@ class WebsocketHandler {
 
     this.printLogs();
 
+    const deletedTransactions = recentlyDeletedTransactions.length ? recentlyDeletedTransactions[0] : [];
+
     const transactionIds = (memPool.limitGBT && candidates) ? Object.keys(candidates?.txs || {}) : Object.keys(newMempool);
     let added = newTransactions;
     let removed = deletedTransactions;
@@ -547,7 +558,7 @@ class WebsocketHandler {
     const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();
     const mempoolInfo = memPool.getMempoolInfo();
     const vBytesPerSecond = memPool.getVBytesPerSecond();
-    const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
+    const rbfTransactions = Common.findRbfTransactions(newTransactions, recentlyDeletedTransactions.flat());
     const da = difficultyAdjustment.getDifficultyAdjustment();
     const accelerations = memPool.getAccelerations();
     memPool.handleRbfTransactions(rbfTransactions);
@@ -578,7 +589,7 @@ class WebsocketHandler {
     const replacedTransactions: { replaced: string, by: TransactionExtended }[] = [];
     for (const tx of newTransactions) {
       if (rbfTransactions[tx.txid]) {
-        for (const replaced of rbfTransactions[tx.txid]) {
+        for (const replaced of rbfTransactions[tx.txid].replaced) {
           replacedTransactions.push({ replaced: replaced.txid, by: tx });
         }
       }
@@ -947,7 +958,7 @@ class WebsocketHandler {
     await accelerationRepository.$indexAccelerationsForBlock(block, accelerations, structuredClone(transactions));
 
     const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
-    memPool.handleMinedRbfTransactions(rbfTransactions);
+    memPool.handleRbfTransactions(rbfTransactions);
     memPool.removeFromSpendMap(transactions);
 
     if (config.MEMPOOL.AUDIT && memPool.isInSync()) {
diff --git a/backend/src/config.ts b/backend/src/config.ts
index cf63a0c7d..a58e05fdd 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -195,7 +195,7 @@ const defaults: IConfig = {
     'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
     'POOLS_UPDATE_DELAY': 604800, // in seconds, default is one week
     'AUDIT': false,
-    'RUST_GBT': false,
+    'RUST_GBT': true,
     'LIMIT_GBT': false,
     'CPFP_INDEXING': false,
     'MAX_BLOCKS_BULK_QUERY': 0,
diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts
index ccbc94bfa..6eee1a9ee 100644
--- a/backend/src/mempool.interfaces.ts
+++ b/backend/src/mempool.interfaces.ts
@@ -299,6 +299,7 @@ export interface BlockExtension {
     id: number; // Note - This is the `unique_id`, not to mix with the auto increment `id`
     name: string;
     slug: string;
+    minerNames: string[] | null;
   };
   avgFee: number;
   avgFeeRate: number;
diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts
index 90100a767..f958e5c8b 100644
--- a/backend/src/repositories/BlocksRepository.ts
+++ b/backend/src/repositories/BlocksRepository.ts
@@ -14,6 +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';
 
 interface DatabaseBlock {
   id: string;
@@ -1054,6 +1055,7 @@ class BlocksRepository {
       id: dbBlk.poolId,
       name: dbBlk.poolName,
       slug: dbBlk.poolSlug,
+      minerNames: null,
     };
     extras.avgFee = dbBlk.avgFee;
     extras.avgFeeRate = dbBlk.avgFeeRate;
@@ -1106,7 +1108,7 @@ class BlocksRepository {
         let summaryVersion = 0;
         if (config.MEMPOOL.BACKEND === 'esplora') {
           const txs = (await bitcoinApi.$getTxsForBlock(dbBlk.id)).map(tx => transactionUtils.extendTransaction(tx));
-          summary = blocks.summarizeBlockTransactions(dbBlk.id, txs);
+          summary = blocks.summarizeBlockTransactions(dbBlk.id, dbBlk.height, txs);
           summaryVersion = 1;
         } else {
           // Call Core RPC
@@ -1123,6 +1125,10 @@ class BlocksRepository {
       }
     }
 
+    if (extras.pool.name === 'OCEAN') {
+      extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
+    }
+
     blk.extras = <BlockExtension>extras;
     return <BlockExtended>blk;
   }
diff --git a/backend/src/utils/api.ts b/backend/src/utils/api.ts
new file mode 100644
index 000000000..69d746b9f
--- /dev/null
+++ b/backend/src/utils/api.ts
@@ -0,0 +1,9 @@
+import { Request, Response } from 'express';
+
+export function handleError(req: Request, res: Response, statusCode: number, errorMessage: string | unknown): void {
+  if (req.accepts('json')) {
+    res.status(statusCode).json({ error: errorMessage });
+  } else {
+    res.status(statusCode).send(errorMessage);
+  }
+}
\ No newline at end of file
diff --git a/backend/src/utils/bitcoin-script.ts b/backend/src/utils/bitcoin-script.ts
index 3414e8269..f9755fcb4 100644
--- a/backend/src/utils/bitcoin-script.ts
+++ b/backend/src/utils/bitcoin-script.ts
@@ -158,7 +158,7 @@ export function parseMultisigScript(script: string): void | { m: number, n: numb
   if (!opN) {
     return;
   }
-  if (!opN.startsWith('OP_PUSHNUM_')) {
+  if (opN !== 'OP_0' && !opN.startsWith('OP_PUSHNUM_')) {
     return;
   }
   const n = parseInt(opN.match(/[0-9]+/)?.[0] || '', 10);
@@ -178,7 +178,7 @@ export function parseMultisigScript(script: string): void | { m: number, n: numb
   if (!opM) {
     return;
   }
-  if (!opM.startsWith('OP_PUSHNUM_')) {
+  if (opM !== 'OP_0' && !opM.startsWith('OP_PUSHNUM_')) {
     return;
   }
   const m = parseInt(opM.match(/[0-9]+/)?.[0] || '', 10);
@@ -200,4 +200,28 @@ export function getVarIntLength(n: number): number {
   } else {
     return 9;
   }
+}
+
+/** Extracts miner names from a DATUM coinbase transaction */
+export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null {
+  let bytes: number[] = [];
+  for (let c = 0; c < coinbaseRaw.length; c += 2) {
+      bytes.push(parseInt(coinbaseRaw.slice(c, c + 2), 16));
+  }
+
+  // Skip block height
+  let tagLengthByte = 1 + bytes[0];
+
+  let tagsLength = bytes[tagLengthByte];
+  if (tagsLength == 0x4c) {
+    tagLengthByte += 1;
+    tagsLength = bytes[tagLengthByte];
+  }
+
+  const tagStart = tagLengthByte + 1;
+  const tags = bytes.slice(tagStart, tagStart + tagsLength);
+  let tagString = String.fromCharCode(...tags);
+  tagString = tagString.replace('\x00', '');
+
+  return tagString.split('\x0f').map((name) => name.replace(/[^a-zA-Z0-9 ]/g, ''));
 }
\ No newline at end of file
diff --git a/docker/backend/start.sh b/docker/backend/start.sh
index 9c98fed44..9e36a2970 100755
--- a/docker/backend/start.sh
+++ b/docker/backend/start.sh
@@ -31,7 +31,7 @@ __MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubuserconte
 __MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master}
 __MEMPOOL_POOLS_UPDATE_DELAY__=${MEMPOOL_POOLS_UPDATE_DELAY:=604800}
 __MEMPOOL_AUDIT__=${MEMPOOL_AUDIT:=false}
-__MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=false}
+__MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=true}
 __MEMPOOL_LIMIT_GBT__=${MEMPOOL_LIMIT_GBT:=false}
 __MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false}
 __MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0}
diff --git a/frontend/README.md b/frontend/README.md
index 069f1d5f0..fb2a5e291 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -33,7 +33,7 @@ $ npm run config:defaults:liquid
 
 ### 3. Run the Frontend
 
-_Make sure to use Node.js 16.10 and npm 7._
+_Make sure to use Node.js 20.x and npm 9.x or newer._
 
 Install project dependencies and run the frontend server:
 
@@ -70,7 +70,7 @@ Set up the [Mempool backend](../backend/) first, if you haven't already.
 
 ### 1. Build the Frontend
 
-_Make sure to use Node.js 16.10 and npm 7._
+_Make sure to use Node.js 20.x and npm 9.x or newer._
 
 Build the frontend:
 
diff --git a/frontend/angular.json b/frontend/angular.json
index 190982225..3aa1cb6a8 100644
--- a/frontend/angular.json
+++ b/frontend/angular.json
@@ -54,6 +54,10 @@
             "translation": "src/locale/messages.fr.xlf",
             "baseHref": "/fr/"
           },
+          "hr": {
+            "translation": "src/locale/messages.hr.xlf",
+            "baseHref": "/hr/"
+          },
           "ja": {
             "translation": "src/locale/messages.ja.xlf",
             "baseHref": "/ja/"
diff --git a/frontend/cypress/fixtures/mainnet_mempoolInfo.json b/frontend/cypress/fixtures/mainnet_mempoolInfo.json
index e0b0fa6b9..584364e9a 100644
--- a/frontend/cypress/fixtures/mainnet_mempoolInfo.json
+++ b/frontend/cypress/fixtures/mainnet_mempoolInfo.json
@@ -750,7 +750,7 @@
     },
     "backendInfo": {
       "hostname": "node205.tk7.mempool.space",
-      "version": "3.0.0-beta",
+      "version": "3.1.0-dev",
       "gitCommit": "abbc8a134",
       "lightning": false
     },
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 37917526c..f7e104bf3 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "mempool-frontend",
-  "version": "3.0.0-beta",
+  "version": "3.1.0-dev",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "mempool-frontend",
-      "version": "3.0.0-beta",
+      "version": "3.1.0-dev",
       "license": "GNU Affero General Public License v3.0",
       "dependencies": {
         "@angular-devkit/build-angular": "^17.3.1",
@@ -34,7 +34,7 @@
         "clipboard": "^2.0.11",
         "domino": "^2.1.6",
         "echarts": "~5.5.0",
-        "esbuild": "^0.23.0",
+        "esbuild": "^0.24.0",
         "lightweight-charts": "~3.8.0",
         "ngx-echarts": "~17.2.0",
         "ngx-infinite-scroll": "^17.0.0",
@@ -42,7 +42,7 @@
         "rxjs": "~7.8.1",
         "tinyify": "^4.0.0",
         "tlite": "^0.1.9",
-        "tslib": "~2.6.0",
+        "tslib": "~2.7.0",
         "zone.js": "~0.14.4"
       },
       "devDependencies": {
@@ -62,7 +62,7 @@
       "optionalDependencies": {
         "@cypress/schematic": "^2.5.0",
         "@types/cypress": "^1.1.3",
-        "cypress": "^13.13.0",
+        "cypress": "^13.15.0",
         "cypress-fail-on-console-error": "~5.1.0",
         "cypress-wait-until": "^2.0.1",
         "mock-socket": "~9.3.1",
@@ -699,6 +699,11 @@
         "node": ">=10"
       }
     },
+    "node_modules/@angular-devkit/build-angular/node_modules/tslib": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+    },
     "node_modules/@angular-devkit/build-webpack": {
       "version": "0.1703.1",
       "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.1.tgz",
@@ -3108,9 +3113,9 @@
       }
     },
     "node_modules/@cypress/request": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz",
-      "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==",
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
+      "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
       "optional": true,
       "dependencies": {
         "aws-sign2": "~0.7.0",
@@ -3119,14 +3124,14 @@
         "combined-stream": "~1.0.6",
         "extend": "~3.0.2",
         "forever-agent": "~0.6.1",
-        "form-data": "~2.3.2",
-        "http-signature": "~1.3.6",
+        "form-data": "~4.0.0",
+        "http-signature": "~1.4.0",
         "is-typedarray": "~1.0.0",
         "isstream": "~0.1.2",
         "json-stringify-safe": "~5.0.1",
         "mime-types": "~2.1.19",
         "performance-now": "^2.1.0",
-        "qs": "6.10.4",
+        "qs": "6.13.0",
         "safe-buffer": "^5.1.2",
         "tough-cookie": "^4.1.3",
         "tunnel-agent": "^0.6.0",
@@ -3196,9 +3201,9 @@
       }
     },
     "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz",
-      "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
+      "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
       "cpu": [
         "ppc64"
       ],
@@ -3211,9 +3216,9 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz",
-      "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
+      "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
       "cpu": [
         "arm"
       ],
@@ -3226,9 +3231,9 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz",
-      "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
+      "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
       "cpu": [
         "arm64"
       ],
@@ -3241,9 +3246,9 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz",
-      "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
+      "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
       "cpu": [
         "x64"
       ],
@@ -3256,9 +3261,9 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
-      "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
+      "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
       "cpu": [
         "arm64"
       ],
@@ -3271,9 +3276,9 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz",
-      "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
+      "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
       "cpu": [
         "x64"
       ],
@@ -3286,9 +3291,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz",
-      "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
       "cpu": [
         "arm64"
       ],
@@ -3301,9 +3306,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz",
-      "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
+      "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
       "cpu": [
         "x64"
       ],
@@ -3316,9 +3321,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz",
-      "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
+      "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
       "cpu": [
         "arm"
       ],
@@ -3331,9 +3336,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz",
-      "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
+      "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
       "cpu": [
         "arm64"
       ],
@@ -3346,9 +3351,9 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz",
-      "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
+      "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
       "cpu": [
         "ia32"
       ],
@@ -3361,9 +3366,9 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz",
-      "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
+      "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
       "cpu": [
         "loong64"
       ],
@@ -3376,9 +3381,9 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz",
-      "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
+      "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
       "cpu": [
         "mips64el"
       ],
@@ -3391,9 +3396,9 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz",
-      "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
+      "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
       "cpu": [
         "ppc64"
       ],
@@ -3406,9 +3411,9 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz",
-      "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
+      "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
       "cpu": [
         "riscv64"
       ],
@@ -3421,9 +3426,9 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz",
-      "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
+      "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
       "cpu": [
         "s390x"
       ],
@@ -3436,9 +3441,9 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz",
-      "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
+      "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
       "cpu": [
         "x64"
       ],
@@ -3451,9 +3456,9 @@
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz",
-      "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
       "cpu": [
         "x64"
       ],
@@ -3466,9 +3471,9 @@
       }
     },
     "node_modules/@esbuild/openbsd-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz",
-      "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
       "cpu": [
         "arm64"
       ],
@@ -3481,9 +3486,9 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz",
-      "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
       "cpu": [
         "x64"
       ],
@@ -3496,9 +3501,9 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz",
-      "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
+      "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
       "cpu": [
         "x64"
       ],
@@ -3511,9 +3516,9 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz",
-      "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
+      "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
       "cpu": [
         "arm64"
       ],
@@ -3526,9 +3531,9 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz",
-      "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
+      "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
       "cpu": [
         "ia32"
       ],
@@ -3541,9 +3546,9 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz",
-      "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
+      "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
       "cpu": [
         "x64"
       ],
@@ -4308,9 +4313,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
-      "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
+      "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
       "cpu": [
         "arm"
       ],
@@ -4320,9 +4325,9 @@
       ]
     },
     "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
-      "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
+      "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
       "cpu": [
         "arm64"
       ],
@@ -4332,9 +4337,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
-      "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
+      "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
       "cpu": [
         "arm64"
       ],
@@ -4344,9 +4349,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
-      "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
+      "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
       "cpu": [
         "x64"
       ],
@@ -4356,9 +4361,21 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
-      "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
+      "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
+      "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
       "cpu": [
         "arm"
       ],
@@ -4368,9 +4385,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
-      "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
+      "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
       "cpu": [
         "arm64"
       ],
@@ -4380,9 +4397,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
-      "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
+      "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
       "cpu": [
         "arm64"
       ],
@@ -4391,10 +4408,22 @@
         "linux"
       ]
     },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
+      "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
     "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
-      "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
+      "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
       "cpu": [
         "riscv64"
       ],
@@ -4403,10 +4432,22 @@
         "linux"
       ]
     },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
+      "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
+      "cpu": [
+        "s390x"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
-      "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
+      "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
       "cpu": [
         "x64"
       ],
@@ -4416,9 +4457,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
-      "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
+      "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
       "cpu": [
         "x64"
       ],
@@ -4428,9 +4469,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
-      "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
+      "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
       "cpu": [
         "arm64"
       ],
@@ -4440,9 +4481,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
-      "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
+      "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
       "cpu": [
         "ia32"
       ],
@@ -4452,9 +4493,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
-      "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
+      "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
       "cpu": [
         "x64"
       ],
@@ -4796,9 +4837,9 @@
       }
     },
     "node_modules/@types/estree": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
-      "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
     },
     "node_modules/@types/express": {
       "version": "4.17.13",
@@ -5792,9 +5833,9 @@
       }
     },
     "node_modules/aws4": {
-      "version": "1.12.0",
-      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
-      "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
+      "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
       "optional": true
     },
     "node_modules/axios": {
@@ -6014,9 +6055,9 @@
       "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
     },
     "node_modules/body-parser": {
-      "version": "1.20.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
-      "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+      "version": "1.20.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
       "dependencies": {
         "bytes": "3.1.2",
         "content-type": "~1.0.5",
@@ -6026,7 +6067,7 @@
         "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "on-finished": "2.4.1",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "raw-body": "2.5.2",
         "type-is": "~1.6.18",
         "unpipe": "1.0.0"
@@ -6060,20 +6101,6 @@
         "node": ">= 0.8"
       }
     },
-    "node_modules/body-parser/node_modules/qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-      "dependencies": {
-        "side-channel": "^1.0.4"
-      },
-      "engines": {
-        "node": ">=0.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/bonjour-service": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz",
@@ -8040,13 +8067,13 @@
       "peer": true
     },
     "node_modules/cypress": {
-      "version": "13.13.0",
-      "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.13.0.tgz",
-      "integrity": "sha512-ou/MQUDq4tcDJI2FsPaod2FZpex4kpIK43JJlcBgWrX8WX7R/05ZxGTuxedOuZBfxjZxja+fbijZGyxiLP6CFA==",
+      "version": "13.15.0",
+      "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz",
+      "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==",
       "hasInstallScript": true,
       "optional": true,
       "dependencies": {
-        "@cypress/request": "^3.0.0",
+        "@cypress/request": "^3.0.4",
         "@cypress/xvfb": "^1.2.4",
         "@types/sinonjs__fake-timers": "8.1.1",
         "@types/sizzle": "^2.3.2",
@@ -8805,9 +8832,9 @@
       "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg=="
     },
     "node_modules/elliptic": {
-      "version": "6.5.4",
-      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
-      "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
+      "version": "6.5.7",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz",
+      "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==",
       "dependencies": {
         "bn.js": "^4.11.9",
         "brorand": "^1.1.0",
@@ -8879,9 +8906,9 @@
       }
     },
     "node_modules/engine.io": {
-      "version": "6.5.1",
-      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz",
-      "integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==",
+      "version": "6.5.5",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
+      "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
       "devOptional": true,
       "dependencies": {
         "@types/cookie": "^0.4.1",
@@ -8892,60 +8919,30 @@
         "cookie": "~0.4.1",
         "cors": "~2.8.5",
         "debug": "~4.3.1",
-        "engine.io-parser": "~5.1.0",
-        "ws": "~8.11.0"
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1"
       },
       "engines": {
-        "node": ">=10.0.0"
+        "node": ">=10.2.0"
       }
     },
     "node_modules/engine.io-client": {
-      "version": "6.5.3",
-      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
-      "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
+      "version": "6.5.4",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
+      "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
       "devOptional": true,
       "dependencies": {
         "@socket.io/component-emitter": "~3.1.0",
         "debug": "~4.3.1",
         "engine.io-parser": "~5.2.1",
-        "ws": "~8.11.0",
+        "ws": "~8.17.1",
         "xmlhttprequest-ssl": "~2.0.0"
       }
     },
-    "node_modules/engine.io-client/node_modules/engine.io-parser": {
-      "version": "5.2.2",
-      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
-      "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
-      "devOptional": true,
-      "engines": {
-        "node": ">=10.0.0"
-      }
-    },
-    "node_modules/engine.io-client/node_modules/ws": {
-      "version": "8.11.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
-      "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
-      "devOptional": true,
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": "^5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/engine.io-parser": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz",
-      "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==",
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
       "devOptional": true,
       "engines": {
         "node": ">=10.0.0"
@@ -8960,27 +8957,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/engine.io/node_modules/ws": {
-      "version": "8.11.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
-      "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
-      "devOptional": true,
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": "^5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/enhanced-resolve": {
       "version": "5.15.0",
       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
@@ -9205,9 +9181,9 @@
       }
     },
     "node_modules/esbuild": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz",
-      "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz",
+      "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
       "hasInstallScript": true,
       "bin": {
         "esbuild": "bin/esbuild"
@@ -9216,30 +9192,30 @@
         "node": ">=18"
       },
       "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.23.0",
-        "@esbuild/android-arm": "0.23.0",
-        "@esbuild/android-arm64": "0.23.0",
-        "@esbuild/android-x64": "0.23.0",
-        "@esbuild/darwin-arm64": "0.23.0",
-        "@esbuild/darwin-x64": "0.23.0",
-        "@esbuild/freebsd-arm64": "0.23.0",
-        "@esbuild/freebsd-x64": "0.23.0",
-        "@esbuild/linux-arm": "0.23.0",
-        "@esbuild/linux-arm64": "0.23.0",
-        "@esbuild/linux-ia32": "0.23.0",
-        "@esbuild/linux-loong64": "0.23.0",
-        "@esbuild/linux-mips64el": "0.23.0",
-        "@esbuild/linux-ppc64": "0.23.0",
-        "@esbuild/linux-riscv64": "0.23.0",
-        "@esbuild/linux-s390x": "0.23.0",
-        "@esbuild/linux-x64": "0.23.0",
-        "@esbuild/netbsd-x64": "0.23.0",
-        "@esbuild/openbsd-arm64": "0.23.0",
-        "@esbuild/openbsd-x64": "0.23.0",
-        "@esbuild/sunos-x64": "0.23.0",
-        "@esbuild/win32-arm64": "0.23.0",
-        "@esbuild/win32-ia32": "0.23.0",
-        "@esbuild/win32-x64": "0.23.0"
+        "@esbuild/aix-ppc64": "0.24.0",
+        "@esbuild/android-arm": "0.24.0",
+        "@esbuild/android-arm64": "0.24.0",
+        "@esbuild/android-x64": "0.24.0",
+        "@esbuild/darwin-arm64": "0.24.0",
+        "@esbuild/darwin-x64": "0.24.0",
+        "@esbuild/freebsd-arm64": "0.24.0",
+        "@esbuild/freebsd-x64": "0.24.0",
+        "@esbuild/linux-arm": "0.24.0",
+        "@esbuild/linux-arm64": "0.24.0",
+        "@esbuild/linux-ia32": "0.24.0",
+        "@esbuild/linux-loong64": "0.24.0",
+        "@esbuild/linux-mips64el": "0.24.0",
+        "@esbuild/linux-ppc64": "0.24.0",
+        "@esbuild/linux-riscv64": "0.24.0",
+        "@esbuild/linux-s390x": "0.24.0",
+        "@esbuild/linux-x64": "0.24.0",
+        "@esbuild/netbsd-x64": "0.24.0",
+        "@esbuild/openbsd-arm64": "0.24.0",
+        "@esbuild/openbsd-x64": "0.24.0",
+        "@esbuild/sunos-x64": "0.24.0",
+        "@esbuild/win32-arm64": "0.24.0",
+        "@esbuild/win32-ia32": "0.24.0",
+        "@esbuild/win32-x64": "0.24.0"
       }
     },
     "node_modules/esbuild-wasm": {
@@ -9870,36 +9846,36 @@
       "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
     },
     "node_modules/express": {
-      "version": "4.19.2",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
-      "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
+      "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
       "dependencies": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.20.2",
+        "body-parser": "1.20.3",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
         "cookie": "0.6.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "1.2.0",
+        "finalhandler": "1.3.1",
         "fresh": "0.5.2",
         "http-errors": "2.0.0",
-        "merge-descriptors": "1.0.1",
+        "merge-descriptors": "1.0.3",
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.7",
+        "path-to-regexp": "0.1.10",
         "proxy-addr": "~2.0.7",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
-        "send": "0.18.0",
-        "serve-static": "1.15.0",
+        "send": "0.19.0",
+        "serve-static": "1.16.2",
         "setprototypeof": "1.2.0",
         "statuses": "2.0.1",
         "type-is": "~1.6.18",
@@ -9918,6 +9894,14 @@
         "ms": "2.0.0"
       }
     },
+    "node_modules/express/node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/express/node_modules/ms": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -9934,20 +9918,6 @@
         "node": ">= 0.8"
       }
     },
-    "node_modules/express/node_modules/qs": {
-      "version": "6.11.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-      "dependencies": {
-        "side-channel": "^1.0.4"
-      },
-      "engines": {
-        "node": ">=0.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/express/node_modules/safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -10172,12 +10142,12 @@
       }
     },
     "node_modules/finalhandler": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
       "dependencies": {
         "debug": "2.6.9",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
@@ -10196,6 +10166,14 @@
         "ms": "2.0.0"
       }
     },
+    "node_modules/finalhandler/node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/finalhandler/node_modules/ms": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -10335,17 +10313,17 @@
       }
     },
     "node_modules/form-data": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
-      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
       "optional": true,
       "dependencies": {
         "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.6",
+        "combined-stream": "^1.0.8",
         "mime-types": "^2.1.12"
       },
       "engines": {
-        "node": ">= 0.12"
+        "node": ">= 6"
       }
     },
     "node_modules/forwarded": {
@@ -10987,14 +10965,14 @@
       }
     },
     "node_modules/http-signature": {
-      "version": "1.3.6",
-      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
-      "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
+      "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
       "optional": true,
       "dependencies": {
         "assert-plus": "^1.0.0",
         "jsprim": "^2.0.2",
-        "sshpk": "^1.14.1"
+        "sshpk": "^1.18.0"
       },
       "engines": {
         "node": ">=0.10"
@@ -12662,9 +12640,12 @@
       }
     },
     "node_modules/merge-descriptors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
     },
     "node_modules/merge-stream": {
       "version": "2.0.0",
@@ -12688,12 +12669,12 @@
       }
     },
     "node_modules/micromatch": {
-      "version": "4.0.4",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
-      "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
       "dependencies": {
-        "braces": "^3.0.1",
-        "picomatch": "^2.2.3"
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
       },
       "engines": {
         "node": ">=8.6"
@@ -13382,9 +13363,9 @@
       "optional": true
     },
     "node_modules/nise/node_modules/path-to-regexp": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
-      "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz",
+      "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==",
       "optional": true,
       "dependencies": {
         "isarray": "0.0.1"
@@ -13669,9 +13650,12 @@
       }
     },
     "node_modules/object-inspect": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
-      "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+      "engines": {
+        "node": ">= 0.4"
+      },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -14185,9 +14169,9 @@
       }
     },
     "node_modules/path-to-regexp": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+      "version": "0.1.10",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
     },
     "node_modules/path-type": {
       "version": "4.0.0",
@@ -14761,12 +14745,11 @@
       }
     },
     "node_modules/qs": {
-      "version": "6.10.4",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz",
-      "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==",
-      "optional": true,
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
       "dependencies": {
-        "side-channel": "^1.0.4"
+        "side-channel": "^1.0.6"
       },
       "engines": {
         "node": ">=0.6"
@@ -15222,11 +15205,11 @@
       }
     },
     "node_modules/rollup": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
-      "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
+      "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
       "dependencies": {
-        "@types/estree": "1.0.5"
+        "@types/estree": "1.0.6"
       },
       "bin": {
         "rollup": "dist/bin/rollup"
@@ -15236,19 +15219,22 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.13.0",
-        "@rollup/rollup-android-arm64": "4.13.0",
-        "@rollup/rollup-darwin-arm64": "4.13.0",
-        "@rollup/rollup-darwin-x64": "4.13.0",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
-        "@rollup/rollup-linux-arm64-gnu": "4.13.0",
-        "@rollup/rollup-linux-arm64-musl": "4.13.0",
-        "@rollup/rollup-linux-riscv64-gnu": "4.13.0",
-        "@rollup/rollup-linux-x64-gnu": "4.13.0",
-        "@rollup/rollup-linux-x64-musl": "4.13.0",
-        "@rollup/rollup-win32-arm64-msvc": "4.13.0",
-        "@rollup/rollup-win32-ia32-msvc": "4.13.0",
-        "@rollup/rollup-win32-x64-msvc": "4.13.0",
+        "@rollup/rollup-android-arm-eabi": "4.24.0",
+        "@rollup/rollup-android-arm64": "4.24.0",
+        "@rollup/rollup-darwin-arm64": "4.24.0",
+        "@rollup/rollup-darwin-x64": "4.24.0",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
+        "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
+        "@rollup/rollup-linux-arm64-gnu": "4.24.0",
+        "@rollup/rollup-linux-arm64-musl": "4.24.0",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
+        "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
+        "@rollup/rollup-linux-s390x-gnu": "4.24.0",
+        "@rollup/rollup-linux-x64-gnu": "4.24.0",
+        "@rollup/rollup-linux-x64-musl": "4.24.0",
+        "@rollup/rollup-win32-arm64-msvc": "4.24.0",
+        "@rollup/rollup-win32-ia32-msvc": "4.24.0",
+        "@rollup/rollup-win32-x64-msvc": "4.24.0",
         "fsevents": "~2.3.2"
       }
     },
@@ -15472,9 +15458,9 @@
       }
     },
     "node_modules/send": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
-      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "version": "0.19.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
       "dependencies": {
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -15613,19 +15599,27 @@
       "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
     },
     "node_modules/serve-static": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
-      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
       "dependencies": {
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
-        "send": "0.18.0"
+        "send": "0.19.0"
       },
       "engines": {
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/serve-static/node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/server-destroy": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
@@ -15717,13 +15711,17 @@
       }
     },
     "node_modules/side-channel": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
       "dependencies": {
-        "call-bind": "^1.0.0",
-        "get-intrinsic": "^1.0.2",
-        "object-inspect": "^1.9.0"
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
+        "object-inspect": "^1.13.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -15913,33 +15911,13 @@
       }
     },
     "node_modules/socket.io-adapter": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
-      "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
+      "version": "2.5.5",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+      "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
       "devOptional": true,
       "dependencies": {
-        "ws": "~8.11.0"
-      }
-    },
-    "node_modules/socket.io-adapter/node_modules/ws": {
-      "version": "8.11.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
-      "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
-      "devOptional": true,
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": "^5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
+        "debug": "~4.3.4",
+        "ws": "~8.17.1"
       }
     },
     "node_modules/socket.io-client": {
@@ -16161,9 +16139,9 @@
       "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
     },
     "node_modules/sshpk": {
-      "version": "1.17.0",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
-      "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+      "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
       "optional": true,
       "dependencies": {
         "asn1": "~0.2.3",
@@ -16757,9 +16735,9 @@
       }
     },
     "node_modules/tough-cookie": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
-      "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
       "optional": true,
       "dependencies": {
         "psl": "^1.1.33",
@@ -16925,9 +16903,9 @@
       }
     },
     "node_modules/tslib": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
-      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+      "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
     },
     "node_modules/tuf-js": {
       "version": "2.2.0",
@@ -17821,30 +17799,16 @@
       }
     },
     "node_modules/wait-on/node_modules/axios": {
-      "version": "1.6.2",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
-      "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
+      "version": "1.7.7",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
+      "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
       "optional": true,
       "dependencies": {
-        "follow-redirects": "^1.15.0",
+        "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
         "proxy-from-env": "^1.1.0"
       }
     },
-    "node_modules/wait-on/node_modules/form-data": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-      "optional": true,
-      "dependencies": {
-        "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.8",
-        "mime-types": "^2.1.12"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
     "node_modules/wait-on/node_modules/proxy-from-env": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -18298,9 +18262,9 @@
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
     },
     "node_modules/ws": {
-      "version": "8.16.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
-      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
       "engines": {
         "node": ">=10.0.0"
       },
@@ -18849,6 +18813,11 @@
           "requires": {
             "lru-cache": "^6.0.0"
           }
+        },
+        "tslib": {
+          "version": "2.6.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+          "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
         }
       }
     },
@@ -20493,9 +20462,9 @@
       }
     },
     "@cypress/request": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz",
-      "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==",
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
+      "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
       "optional": true,
       "requires": {
         "aws-sign2": "~0.7.0",
@@ -20504,14 +20473,14 @@
         "combined-stream": "~1.0.6",
         "extend": "~3.0.2",
         "forever-agent": "~0.6.1",
-        "form-data": "~2.3.2",
-        "http-signature": "~1.3.6",
+        "form-data": "~4.0.0",
+        "http-signature": "~1.4.0",
         "is-typedarray": "~1.0.0",
         "isstream": "~0.1.2",
         "json-stringify-safe": "~5.0.1",
         "mime-types": "~2.1.19",
         "performance-now": "^2.1.0",
-        "qs": "6.10.4",
+        "qs": "6.13.0",
         "safe-buffer": "^5.1.2",
         "tough-cookie": "^4.1.3",
         "tunnel-agent": "^0.6.0",
@@ -20572,147 +20541,147 @@
       "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
     },
     "@esbuild/aix-ppc64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz",
-      "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
+      "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
       "optional": true
     },
     "@esbuild/android-arm": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz",
-      "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
+      "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
       "optional": true
     },
     "@esbuild/android-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz",
-      "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
+      "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
       "optional": true
     },
     "@esbuild/android-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz",
-      "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
+      "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
       "optional": true
     },
     "@esbuild/darwin-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
-      "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
+      "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
       "optional": true
     },
     "@esbuild/darwin-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz",
-      "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
+      "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
       "optional": true
     },
     "@esbuild/freebsd-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz",
-      "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
       "optional": true
     },
     "@esbuild/freebsd-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz",
-      "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
+      "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
       "optional": true
     },
     "@esbuild/linux-arm": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz",
-      "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
+      "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
       "optional": true
     },
     "@esbuild/linux-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz",
-      "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
+      "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
       "optional": true
     },
     "@esbuild/linux-ia32": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz",
-      "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
+      "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
       "optional": true
     },
     "@esbuild/linux-loong64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz",
-      "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
+      "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
       "optional": true
     },
     "@esbuild/linux-mips64el": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz",
-      "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
+      "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
       "optional": true
     },
     "@esbuild/linux-ppc64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz",
-      "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
+      "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
       "optional": true
     },
     "@esbuild/linux-riscv64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz",
-      "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
+      "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
       "optional": true
     },
     "@esbuild/linux-s390x": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz",
-      "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
+      "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
       "optional": true
     },
     "@esbuild/linux-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz",
-      "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
+      "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
       "optional": true
     },
     "@esbuild/netbsd-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz",
-      "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
       "optional": true
     },
     "@esbuild/openbsd-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz",
-      "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
       "optional": true
     },
     "@esbuild/openbsd-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz",
-      "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
       "optional": true
     },
     "@esbuild/sunos-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz",
-      "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
+      "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
       "optional": true
     },
     "@esbuild/win32-arm64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz",
-      "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
+      "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
       "optional": true
     },
     "@esbuild/win32-ia32": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz",
-      "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
+      "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
       "optional": true
     },
     "@esbuild/win32-x64": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz",
-      "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
+      "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
       "optional": true
     },
     "@eslint-community/eslint-utils": {
@@ -21256,81 +21225,99 @@
       "peer": true
     },
     "@rollup/rollup-android-arm-eabi": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
-      "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
+      "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
       "optional": true
     },
     "@rollup/rollup-android-arm64": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
-      "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
+      "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
       "optional": true
     },
     "@rollup/rollup-darwin-arm64": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
-      "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
+      "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
       "optional": true
     },
     "@rollup/rollup-darwin-x64": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
-      "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
+      "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
       "optional": true
     },
     "@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
-      "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
+      "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
+      "optional": true
+    },
+    "@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
+      "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
       "optional": true
     },
     "@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
-      "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
+      "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
       "optional": true
     },
     "@rollup/rollup-linux-arm64-musl": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
-      "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
+      "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
+      "optional": true
+    },
+    "@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
+      "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
       "optional": true
     },
     "@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
-      "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
+      "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
+      "optional": true
+    },
+    "@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
+      "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
       "optional": true
     },
     "@rollup/rollup-linux-x64-gnu": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
-      "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
+      "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
       "optional": true
     },
     "@rollup/rollup-linux-x64-musl": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
-      "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
+      "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
       "optional": true
     },
     "@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
-      "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
+      "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
       "optional": true
     },
     "@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
-      "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
+      "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
       "optional": true
     },
     "@rollup/rollup-win32-x64-msvc": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
-      "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
+      "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
       "optional": true
     },
     "@schematics/angular": {
@@ -21634,9 +21621,9 @@
       }
     },
     "@types/estree": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
-      "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
     },
     "@types/express": {
       "version": "4.17.13",
@@ -22396,9 +22383,9 @@
       "optional": true
     },
     "aws4": {
-      "version": "1.12.0",
-      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
-      "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
+      "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
       "optional": true
     },
     "axios": {
@@ -22572,9 +22559,9 @@
       "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
     },
     "body-parser": {
-      "version": "1.20.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
-      "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+      "version": "1.20.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
       "requires": {
         "bytes": "3.1.2",
         "content-type": "~1.0.5",
@@ -22584,7 +22571,7 @@
         "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "on-finished": "2.4.1",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "raw-body": "2.5.2",
         "type-is": "~1.6.18",
         "unpipe": "1.0.0"
@@ -22610,14 +22597,6 @@
           "requires": {
             "ee-first": "1.1.1"
           }
-        },
-        "qs": {
-          "version": "6.11.0",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-          "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-          "requires": {
-            "side-channel": "^1.0.4"
-          }
         }
       }
     },
@@ -24127,12 +24106,12 @@
       "peer": true
     },
     "cypress": {
-      "version": "13.13.0",
-      "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.13.0.tgz",
-      "integrity": "sha512-ou/MQUDq4tcDJI2FsPaod2FZpex4kpIK43JJlcBgWrX8WX7R/05ZxGTuxedOuZBfxjZxja+fbijZGyxiLP6CFA==",
+      "version": "13.15.0",
+      "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz",
+      "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==",
       "optional": true,
       "requires": {
-        "@cypress/request": "^3.0.0",
+        "@cypress/request": "^3.0.4",
         "@cypress/xvfb": "^1.2.4",
         "@types/sinonjs__fake-timers": "8.1.1",
         "@types/sizzle": "^2.3.2",
@@ -24723,9 +24702,9 @@
       "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg=="
     },
     "elliptic": {
-      "version": "6.5.4",
-      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
-      "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
+      "version": "6.5.7",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz",
+      "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==",
       "requires": {
         "bn.js": "^4.11.9",
         "brorand": "^1.1.0",
@@ -24792,9 +24771,9 @@
       }
     },
     "engine.io": {
-      "version": "6.5.1",
-      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz",
-      "integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==",
+      "version": "6.5.5",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
+      "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
       "devOptional": true,
       "requires": {
         "@types/cookie": "^0.4.1",
@@ -24805,8 +24784,8 @@
         "cookie": "~0.4.1",
         "cors": "~2.8.5",
         "debug": "~4.3.1",
-        "engine.io-parser": "~5.1.0",
-        "ws": "~8.11.0"
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1"
       },
       "dependencies": {
         "cookie": {
@@ -24814,48 +24793,26 @@
           "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
           "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
           "devOptional": true
-        },
-        "ws": {
-          "version": "8.11.0",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
-          "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
-          "devOptional": true,
-          "requires": {}
         }
       }
     },
     "engine.io-client": {
-      "version": "6.5.3",
-      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
-      "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
+      "version": "6.5.4",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
+      "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
       "devOptional": true,
       "requires": {
         "@socket.io/component-emitter": "~3.1.0",
         "debug": "~4.3.1",
         "engine.io-parser": "~5.2.1",
-        "ws": "~8.11.0",
+        "ws": "~8.17.1",
         "xmlhttprequest-ssl": "~2.0.0"
-      },
-      "dependencies": {
-        "engine.io-parser": {
-          "version": "5.2.2",
-          "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
-          "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
-          "devOptional": true
-        },
-        "ws": {
-          "version": "8.11.0",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
-          "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
-          "devOptional": true,
-          "requires": {}
-        }
       }
     },
     "engine.io-parser": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz",
-      "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==",
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
       "devOptional": true
     },
     "enhanced-resolve": {
@@ -25044,34 +25001,34 @@
       }
     },
     "esbuild": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz",
-      "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==",
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz",
+      "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
       "requires": {
-        "@esbuild/aix-ppc64": "0.23.0",
-        "@esbuild/android-arm": "0.23.0",
-        "@esbuild/android-arm64": "0.23.0",
-        "@esbuild/android-x64": "0.23.0",
-        "@esbuild/darwin-arm64": "0.23.0",
-        "@esbuild/darwin-x64": "0.23.0",
-        "@esbuild/freebsd-arm64": "0.23.0",
-        "@esbuild/freebsd-x64": "0.23.0",
-        "@esbuild/linux-arm": "0.23.0",
-        "@esbuild/linux-arm64": "0.23.0",
-        "@esbuild/linux-ia32": "0.23.0",
-        "@esbuild/linux-loong64": "0.23.0",
-        "@esbuild/linux-mips64el": "0.23.0",
-        "@esbuild/linux-ppc64": "0.23.0",
-        "@esbuild/linux-riscv64": "0.23.0",
-        "@esbuild/linux-s390x": "0.23.0",
-        "@esbuild/linux-x64": "0.23.0",
-        "@esbuild/netbsd-x64": "0.23.0",
-        "@esbuild/openbsd-arm64": "0.23.0",
-        "@esbuild/openbsd-x64": "0.23.0",
-        "@esbuild/sunos-x64": "0.23.0",
-        "@esbuild/win32-arm64": "0.23.0",
-        "@esbuild/win32-ia32": "0.23.0",
-        "@esbuild/win32-x64": "0.23.0"
+        "@esbuild/aix-ppc64": "0.24.0",
+        "@esbuild/android-arm": "0.24.0",
+        "@esbuild/android-arm64": "0.24.0",
+        "@esbuild/android-x64": "0.24.0",
+        "@esbuild/darwin-arm64": "0.24.0",
+        "@esbuild/darwin-x64": "0.24.0",
+        "@esbuild/freebsd-arm64": "0.24.0",
+        "@esbuild/freebsd-x64": "0.24.0",
+        "@esbuild/linux-arm": "0.24.0",
+        "@esbuild/linux-arm64": "0.24.0",
+        "@esbuild/linux-ia32": "0.24.0",
+        "@esbuild/linux-loong64": "0.24.0",
+        "@esbuild/linux-mips64el": "0.24.0",
+        "@esbuild/linux-ppc64": "0.24.0",
+        "@esbuild/linux-riscv64": "0.24.0",
+        "@esbuild/linux-s390x": "0.24.0",
+        "@esbuild/linux-x64": "0.24.0",
+        "@esbuild/netbsd-x64": "0.24.0",
+        "@esbuild/openbsd-arm64": "0.24.0",
+        "@esbuild/openbsd-x64": "0.24.0",
+        "@esbuild/sunos-x64": "0.24.0",
+        "@esbuild/win32-arm64": "0.24.0",
+        "@esbuild/win32-ia32": "0.24.0",
+        "@esbuild/win32-x64": "0.24.0"
       }
     },
     "esbuild-wasm": {
@@ -25540,36 +25497,36 @@
       "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
     },
     "express": {
-      "version": "4.19.2",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
-      "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
+      "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
       "requires": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.20.2",
+        "body-parser": "1.20.3",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
         "cookie": "0.6.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
         "depd": "2.0.0",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "1.2.0",
+        "finalhandler": "1.3.1",
         "fresh": "0.5.2",
         "http-errors": "2.0.0",
-        "merge-descriptors": "1.0.1",
+        "merge-descriptors": "1.0.3",
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.7",
+        "path-to-regexp": "0.1.10",
         "proxy-addr": "~2.0.7",
-        "qs": "6.11.0",
+        "qs": "6.13.0",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
-        "send": "0.18.0",
-        "serve-static": "1.15.0",
+        "send": "0.19.0",
+        "serve-static": "1.16.2",
         "setprototypeof": "1.2.0",
         "statuses": "2.0.1",
         "type-is": "~1.6.18",
@@ -25585,6 +25542,11 @@
             "ms": "2.0.0"
           }
         },
+        "encodeurl": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+          "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
+        },
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -25598,14 +25560,6 @@
             "ee-first": "1.1.1"
           }
         },
-        "qs": {
-          "version": "6.11.0",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-          "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-          "requires": {
-            "side-channel": "^1.0.4"
-          }
-        },
         "safe-buffer": {
           "version": "5.2.1",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -25778,12 +25732,12 @@
       }
     },
     "finalhandler": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
       "requires": {
         "debug": "2.6.9",
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
@@ -25799,6 +25753,11 @@
             "ms": "2.0.0"
           }
         },
+        "encodeurl": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+          "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
+        },
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -25892,13 +25851,13 @@
       "optional": true
     },
     "form-data": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
-      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
       "optional": true,
       "requires": {
         "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.6",
+        "combined-stream": "^1.0.8",
         "mime-types": "^2.1.12"
       }
     },
@@ -26360,14 +26319,14 @@
       }
     },
     "http-signature": {
-      "version": "1.3.6",
-      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
-      "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
+      "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
       "optional": true,
       "requires": {
         "assert-plus": "^1.0.0",
         "jsprim": "^2.0.2",
-        "sshpk": "^1.14.1"
+        "sshpk": "^1.18.0"
       }
     },
     "https-browserify": {
@@ -27591,9 +27550,9 @@
       }
     },
     "merge-descriptors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="
     },
     "merge-stream": {
       "version": "2.0.0",
@@ -27611,12 +27570,12 @@
       "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
     },
     "micromatch": {
-      "version": "4.0.4",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
-      "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
       "requires": {
-        "braces": "^3.0.1",
-        "picomatch": "^2.2.3"
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
       }
     },
     "miller-rabin": {
@@ -28156,9 +28115,9 @@
           "optional": true
         },
         "path-to-regexp": {
-          "version": "1.8.0",
-          "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
-          "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+          "version": "1.9.0",
+          "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz",
+          "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==",
           "optional": true,
           "requires": {
             "isarray": "0.0.1"
@@ -28364,9 +28323,9 @@
       "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
     },
     "object-inspect": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
-      "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw=="
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g=="
     },
     "object-keys": {
       "version": "1.1.1",
@@ -28740,9 +28699,9 @@
       }
     },
     "path-to-regexp": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+      "version": "0.1.10",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
     },
     "path-type": {
       "version": "4.0.0",
@@ -29137,12 +29096,11 @@
       }
     },
     "qs": {
-      "version": "6.10.4",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz",
-      "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==",
-      "optional": true,
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
       "requires": {
-        "side-channel": "^1.0.4"
+        "side-channel": "^1.0.6"
       }
     },
     "querystring": {
@@ -29495,24 +29453,27 @@
       }
     },
     "rollup": {
-      "version": "4.13.0",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
-      "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
+      "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
       "requires": {
-        "@rollup/rollup-android-arm-eabi": "4.13.0",
-        "@rollup/rollup-android-arm64": "4.13.0",
-        "@rollup/rollup-darwin-arm64": "4.13.0",
-        "@rollup/rollup-darwin-x64": "4.13.0",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
-        "@rollup/rollup-linux-arm64-gnu": "4.13.0",
-        "@rollup/rollup-linux-arm64-musl": "4.13.0",
-        "@rollup/rollup-linux-riscv64-gnu": "4.13.0",
-        "@rollup/rollup-linux-x64-gnu": "4.13.0",
-        "@rollup/rollup-linux-x64-musl": "4.13.0",
-        "@rollup/rollup-win32-arm64-msvc": "4.13.0",
-        "@rollup/rollup-win32-ia32-msvc": "4.13.0",
-        "@rollup/rollup-win32-x64-msvc": "4.13.0",
-        "@types/estree": "1.0.5",
+        "@rollup/rollup-android-arm-eabi": "4.24.0",
+        "@rollup/rollup-android-arm64": "4.24.0",
+        "@rollup/rollup-darwin-arm64": "4.24.0",
+        "@rollup/rollup-darwin-x64": "4.24.0",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
+        "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
+        "@rollup/rollup-linux-arm64-gnu": "4.24.0",
+        "@rollup/rollup-linux-arm64-musl": "4.24.0",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
+        "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
+        "@rollup/rollup-linux-s390x-gnu": "4.24.0",
+        "@rollup/rollup-linux-x64-gnu": "4.24.0",
+        "@rollup/rollup-linux-x64-musl": "4.24.0",
+        "@rollup/rollup-win32-arm64-msvc": "4.24.0",
+        "@rollup/rollup-win32-ia32-msvc": "4.24.0",
+        "@rollup/rollup-win32-x64-msvc": "4.24.0",
+        "@types/estree": "1.0.6",
         "fsevents": "~2.3.2"
       }
     },
@@ -29663,9 +29624,9 @@
       }
     },
     "send": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
-      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "version": "0.19.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
       "requires": {
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -29786,14 +29747,21 @@
       }
     },
     "serve-static": {
-      "version": "1.15.0",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
-      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
       "requires": {
-        "encodeurl": "~1.0.2",
+        "encodeurl": "~2.0.0",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
-        "send": "0.18.0"
+        "send": "0.19.0"
+      },
+      "dependencies": {
+        "encodeurl": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+          "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
+        }
       }
     },
     "server-destroy": {
@@ -29869,13 +29837,14 @@
       "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA=="
     },
     "side-channel": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
-      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
       "requires": {
-        "call-bind": "^1.0.0",
-        "get-intrinsic": "^1.0.2",
-        "object-inspect": "^1.9.0"
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
+        "object-inspect": "^1.13.1"
       }
     },
     "signal-exit": {
@@ -30008,21 +29977,13 @@
       }
     },
     "socket.io-adapter": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
-      "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
+      "version": "2.5.5",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+      "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
       "devOptional": true,
       "requires": {
-        "ws": "~8.11.0"
-      },
-      "dependencies": {
-        "ws": {
-          "version": "8.11.0",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
-          "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
-          "devOptional": true,
-          "requires": {}
-        }
+        "debug": "~4.3.4",
+        "ws": "~8.17.1"
       }
     },
     "socket.io-client": {
@@ -30206,9 +30167,9 @@
       "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
     },
     "sshpk": {
-      "version": "1.17.0",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
-      "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+      "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
       "optional": true,
       "requires": {
         "asn1": "~0.2.3",
@@ -30654,9 +30615,9 @@
       "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
     },
     "tough-cookie": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
-      "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
       "optional": true,
       "requires": {
         "psl": "^1.1.33",
@@ -30763,9 +30724,9 @@
       }
     },
     "tslib": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
-      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+      "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
     },
     "tuf-js": {
       "version": "2.2.0",
@@ -31277,27 +31238,16 @@
       },
       "dependencies": {
         "axios": {
-          "version": "1.6.2",
-          "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
-          "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
+          "version": "1.7.7",
+          "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
+          "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
           "optional": true,
           "requires": {
-            "follow-redirects": "^1.15.0",
+            "follow-redirects": "^1.15.6",
             "form-data": "^4.0.0",
             "proxy-from-env": "^1.1.0"
           }
         },
-        "form-data": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-          "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-          "optional": true,
-          "requires": {
-            "asynckit": "^0.4.0",
-            "combined-stream": "^1.0.8",
-            "mime-types": "^2.1.12"
-          }
-        },
         "proxy-from-env": {
           "version": "1.1.0",
           "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -31612,9 +31562,9 @@
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
     },
     "ws": {
-      "version": "8.16.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
-      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
       "requires": {}
     },
     "xhr2": {
diff --git a/frontend/package.json b/frontend/package.json
index c810cac00..3318d5031 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mempool-frontend",
-  "version": "3.0.0-beta",
+  "version": "3.1.0-dev",
   "description": "Bitcoin mempool visualizer and blockchain explorer backend",
   "license": "GNU Affero General Public License v3.0",
   "homepage": "https://mempool.space",
@@ -92,10 +92,10 @@
     "ngx-infinite-scroll": "^17.0.0",
     "qrcode": "1.5.1",
     "rxjs": "~7.8.1",
-    "esbuild": "^0.23.0",
+    "esbuild": "^0.24.0",
     "tinyify": "^4.0.0",
     "tlite": "^0.1.9",
-    "tslib": "~2.6.0",
+    "tslib": "~2.7.0",
     "zone.js": "~0.14.4"
   },
   "devDependencies": {
@@ -115,7 +115,7 @@
   "optionalDependencies": {
     "@cypress/schematic": "^2.5.0",
     "@types/cypress": "^1.1.3",
-    "cypress": "^13.13.0",
+    "cypress": "^13.15.0",
     "cypress-fail-on-console-error": "~5.1.0",
     "cypress-wait-until": "^2.0.1",
     "mock-socket": "~9.3.1",
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 50bbd88b9..d1129a602 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -21,6 +21,7 @@ import { StorageService } from './services/storage.service';
 import { HttpCacheInterceptor } from './services/http-cache.interceptor';
 import { LanguageService } from './services/language.service';
 import { ThemeService } from './services/theme.service';
+import { TimeService } from './services/time.service';
 import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
 import { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe';
 import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
@@ -42,6 +43,7 @@ const providers = [
   EnterpriseService,
   LanguageService,
   ThemeService,
+  TimeService,
   ShortenStringPipe,
   FiatShortenerPipe,
   FiatCurrencyPipe,
diff --git a/frontend/src/app/bitcoin.utils.ts b/frontend/src/app/bitcoin.utils.ts
index 92d3de7f3..ae522121c 100644
--- a/frontend/src/app/bitcoin.utils.ts
+++ b/frontend/src/app/bitcoin.utils.ts
@@ -135,7 +135,7 @@ export function parseMultisigScript(script: string): void | { m: number, n: numb
     return;
   }
   const opN = ops.pop();
-  if (!opN.startsWith('OP_PUSHNUM_')) {
+  if (opN !== 'OP_0' && !opN.startsWith('OP_PUSHNUM_')) {
     return;
   }
   const n = parseInt(opN.match(/[0-9]+/)[0], 10);
@@ -152,7 +152,7 @@ export function parseMultisigScript(script: string): void | { m: number, n: numb
     }
   }
   const opM = ops.pop();
-  if (!opM.startsWith('OP_PUSHNUM_')) {
+  if (opM !== 'OP_0' && !opM.startsWith('OP_PUSHNUM_')) {
     return;
   }
   const m = parseInt(opM.match(/[0-9]+/)[0], 10);
diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html
index 41c0ce47f..406835572 100644
--- a/frontend/src/app/components/about/about.component.html
+++ b/frontend/src/app/components/about/about.component.html
@@ -53,7 +53,7 @@
         <span>Spiral</span>
       </a>
       <a href="https://foundrydigital.com/" target="_blank" title="Foundry">
-        <svg xmlns="http://www.w3.org/2000/svg" id="b" data-name="Layer 2" style="zoom: 1;" width="32" height="76" viewBox="0 0 32 76">
+        <svg xmlns="http://www.w3.org/2000/svg" id="b" data-name="Layer 2" style="zoom: 1;" width="32" height="90" viewBox="0 -5 32 90" class="image">
           <defs>
             <style>
               .d {
@@ -125,17 +125,14 @@
         <span>Blockstream</span>
       </a>
       <a href="https://unchained.com/" target="_blank" title="Unchained">
-        <svg id="Layer_1" width="78" height="78" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156.68 156.68"><defs><style>.cls-unchained-1{fill:#fff;}</style></defs><path class="cls-unchained-1" d="m78.34,0C35.07,0,0,35.07,0,78.34s35.07,78.34,78.34,78.34,78.34-35.07,78.34-78.34S121.6,0,78.34,0ZM20.23,109.5c-4.99-9.28-7.81-19.89-7.81-31.16C12.42,41.93,41.93,12.42,78.34,12.42c33.15,0,60.58,24.46,65.23,56.32h-37.48c-45.29,0-71.19,20.05-85.85,40.76Zm58.11,34.76c-12.42,0-24.04-3.44-33.96-9.41,3.94-8.85,9.11-18.7,15.84-28.9,20.99-31.8,52.2-31.19,76.49-31.19h7.45c.06,1.18.1,2.38.1,3.58,0,36.41-29.51,65.92-65.92,65.92Z"/><path class="cls-unchained-1" d="m91.98,42.4l-3.62-1.18c-3.94-1.29-7.03-4.38-8.32-8.32l-1.18-3.63c-.13-.39-.68-.39-.81,0l-1.18,3.63c-1.29,3.94-4.38,7.03-8.32,8.32l-3.62,1.18c-.39.13-.39.68,0,.81l3.62,1.18c3.94,1.29,7.03,4.38,8.32,8.32l1.18,3.63c.13.39.68.39.81,0l1.18-3.63c1.29-3.94,4.38-7.03,8.32-8.32l3.62-1.18c.39-.13.39-.68,0-.81Z"/></svg>
+        <svg id="Layer_1" width="78" height="78" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156.68 156.68" class="image">
+          <defs><style>.cls-unchained-1{fill:#fff;}</style></defs><path class="cls-unchained-1" d="m78.34,0C35.07,0,0,35.07,0,78.34s35.07,78.34,78.34,78.34,78.34-35.07,78.34-78.34S121.6,0,78.34,0ZM20.23,109.5c-4.99-9.28-7.81-19.89-7.81-31.16C12.42,41.93,41.93,12.42,78.34,12.42c33.15,0,60.58,24.46,65.23,56.32h-37.48c-45.29,0-71.19,20.05-85.85,40.76Zm58.11,34.76c-12.42,0-24.04-3.44-33.96-9.41,3.94-8.85,9.11-18.7,15.84-28.9,20.99-31.8,52.2-31.19,76.49-31.19h7.45c.06,1.18.1,2.38.1,3.58,0,36.41-29.51,65.92-65.92,65.92Z"/><path class="cls-unchained-1" d="m91.98,42.4l-3.62-1.18c-3.94-1.29-7.03-4.38-8.32-8.32l-1.18-3.63c-.13-.39-.68-.39-.81,0l-1.18,3.63c-1.29,3.94-4.38,7.03-8.32,8.32l-3.62,1.18c-.39.13-.39.68,0,.81l3.62,1.18c3.94,1.29,7.03,4.38,8.32,8.32l1.18,3.63c.13.39.68.39.81,0l1.18-3.63c1.29-3.94,4.38-7.03,8.32-8.32l3.62-1.18c.39-.13.39-.68,0-.81Z"/>
+        </svg>
         <span>Unchained</span>
       </a>
-      <a href="https://gemini.com/" target="_blank" title="Gemini">
-        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="360" height="360" viewBox="0 0 360 360" class="image">
-          <rect style="fill: black" width="360" height="360" />
-          <g transform="matrix(0.62 0 0 0.62 180 180)">
-            <path style="fill: rgb(0,220,250)" transform=" translate(-162, -162)" d="M 211.74 0 C 154.74 0 106.35 43.84 100.25 100.25 C 43.84 106.35 1.4210854715202004e-14 154.76 1.4210854715202004e-14 211.74 C 0.044122601308501076 273.7212006364817 50.27879936351834 323.95587739869154 112.26 324 C 169.26 324 217.84 280.15999999999997 223.75 223.75 C 280.15999999999997 217.65 324 169.24 324 112.26 C 323.95587739869154 50.278799363518324 273.72120063648174 0.04412260130848722 211.74 -1.4210854715202004e-14 z M 297.74 124.84 C 291.9644950552469 162.621439649343 262.2969457716857 192.26062994820046 224.51 198 L 224.51 124.84 z M 26.3 199.16 C 31.986912917108594 161.30935034910615 61.653433460549415 131.56986937804106 99.48999999999998 125.78999999999999 L 99.49 199 L 26.3 199 z M 198.21 224.51 C 191.87736076583954 267.0991541201681 155.312384597087 298.62923417787493 112.255 298.62923417787493 C 69.19761540291302 298.62923417787493 32.63263923416048 267.0991541201682 26.3 224.51 z M 199.16 124.83999999999999 L 199.16 199 L 124.84 199 L 124.84 124.84 z M 297.7 99.48999999999998 L 125.78999999999999 99.48999999999998 C 132.12263923416046 56.90084587983182 168.687615402913 25.37076582212505 211.745 25.37076582212505 C 254.80238459708698 25.37076582212505 291.3673607658395 56.900845879831834 297.7 99.49 z" stroke-linecap="round" />
-          </g>
-        </svg>
-        <span>Gemini</span>
+      <a href="https://bitkey.world/" target="_blank" title="Bitkey">
+        <img class="image" src="/resources/profile/bitkey.svg" />
+        <span>Bitkey</span>
       </a>
       <a href="https://bullbitcoin.com/" target="_blank" title="Bull Bitcoin">
         <svg aria-hidden="true" class="image" viewBox="0 -5 40 40" xmlns="http://www.w3.org/2000/svg">
@@ -150,7 +147,7 @@
         <span>Bull Bitcoin</span>
       </a>
       <a href="https://exodus.com/" target="_blank" title="Exodus">
-        <svg width="80" height="80" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <svg width="80" height="80" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg" class="image">
           <circle cx="250" cy="250" r="250" fill="#1F2033"/>
           <g clip-path="url(#clip0_2_14)">
             <path d="M411.042 178.303L271.79 87V138.048L361.121 196.097L350.612 229.351H271.79V271.648H350.612L361.121 304.903L271.79 362.952V414L411.042 322.989L388.271 250.646L411.042 178.303Z" fill="url(#paint0_linear_2_14)"/>
@@ -191,6 +188,19 @@
         </svg>
         <span>Exodus</span>
       </a>
+      <a href="https://gemini.com/" target="_blank" title="Gemini">
+        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="360" height="360" viewBox="0 0 360 360" class="image">
+          <rect style="fill: black" width="360" height="360" />
+          <g transform="matrix(0.62 0 0 0.62 180 180)">
+            <path style="fill: rgb(0,220,250)" transform=" translate(-162, -162)" d="M 211.74 0 C 154.74 0 106.35 43.84 100.25 100.25 C 43.84 106.35 1.4210854715202004e-14 154.76 1.4210854715202004e-14 211.74 C 0.044122601308501076 273.7212006364817 50.27879936351834 323.95587739869154 112.26 324 C 169.26 324 217.84 280.15999999999997 223.75 223.75 C 280.15999999999997 217.65 324 169.24 324 112.26 C 323.95587739869154 50.278799363518324 273.72120063648174 0.04412260130848722 211.74 -1.4210854715202004e-14 z M 297.74 124.84 C 291.9644950552469 162.621439649343 262.2969457716857 192.26062994820046 224.51 198 L 224.51 124.84 z M 26.3 199.16 C 31.986912917108594 161.30935034910615 61.653433460549415 131.56986937804106 99.48999999999998 125.78999999999999 L 99.49 199 L 26.3 199 z M 198.21 224.51 C 191.87736076583954 267.0991541201681 155.312384597087 298.62923417787493 112.255 298.62923417787493 C 69.19761540291302 298.62923417787493 32.63263923416048 267.0991541201682 26.3 224.51 z M 199.16 124.83999999999999 L 199.16 199 L 124.84 199 L 124.84 124.84 z M 297.7 99.48999999999998 L 125.78999999999999 99.48999999999998 C 132.12263923416046 56.90084587983182 168.687615402913 25.37076582212505 211.745 25.37076582212505 C 254.80238459708698 25.37076582212505 291.3673607658395 56.900845879831834 297.7 99.49 z" stroke-linecap="round" />
+          </g>
+        </svg>
+        <span>Gemini</span>
+      </a>
+      <a href="https://leather.io/" target="_blank" title="Leather">
+        <img class="image" src="/resources/profile/leather.svg" />
+        <span>Leather</span>
+      </a>
     </div>
   </div>
 
@@ -435,7 +445,7 @@
       Trademark Notice<br>
     </div>
     <p>
-      The Mempool Open Source Project&reg;, Mempool Accelerator&trade;, Mempool Enterprise&reg;, Mempool Liquidity&trade;, mempool.space&reg;, Be your own explorer&trade;, Explore the full Bitcoin ecosystem&reg;, Mempool Goggles&trade;, the mempool logo, the mempool Square logo, the mempool Blocks logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical Logo, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
+      The Mempool Open Source Project&reg;, Mempool Accelerator&trade;, Mempool Enterprise&reg;, Mempool Liquidity&trade;, mempool.space&reg;, Be your own explorer&trade;, Explore the full Bitcoin ecosystem&reg;, Mempool Goggles&trade;, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool transaction Logo, the mempool Blocks 3 | 2 Logo, the mempool research Logo, the mempool.space Vertical Logo, and the mempool.space Horizontal Logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
     </p>
     <p>
       While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our <a href="https://mempool.space/trademark-policy">Trademark Policy and Guidelines</a> for more details, published on &lt;https://mempool.space/trademark-policy&gt;.
diff --git a/frontend/src/app/components/about/about.component.scss b/frontend/src/app/components/about/about.component.scss
index a360e180c..6a20239cc 100644
--- a/frontend/src/app/components/about/about.component.scss
+++ b/frontend/src/app/components/about/about.component.scss
@@ -13,8 +13,6 @@
 
   .image.not-rounded {
     border-radius: 0;
-    width: 60px;
-    height: 60px;
   }
 
   .intro {
@@ -158,9 +156,8 @@
           margin: 40px 29px 10px;
           &.image.coldcard {
             border-radius: 0;
-            width: auto;
-            max-height: 50px;
-            margin: 40px 29px 14px 29px;
+            height: auto;
+            margin: 20px 29px 20px;
           }
         }
       }
@@ -254,3 +251,12 @@
   width: 64px;
   height: 64px;
 }
+
+.enterprise-sponsor {
+  .wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    max-width: 800px; 
+  }
+}
\ No newline at end of file
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 6b1eadf7d..162594cd6 100644
--- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts
+++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts
@@ -75,6 +75,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
   @Output() changeMode = new EventEmitter<boolean>();
 
   calculating = true;
+  processing = false;
   selectedOption: 'wait' | 'accel';
   cantPayReason = '';
   quoteError = ''; // error fetching estimate or initial data
@@ -196,9 +197,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
     if (changes.scrollEvent && this.scrollEvent) {
       this.scrollToElement('acceleratePreviewAnchor', 'start');
     }
-    if (changes.accelerating) {
-      if ((this.step === 'processing' || this.step === 'paid') && this.accelerating) {
+    if (changes.accelerating && this.accelerating) {
+      if (this.step === 'processing' || this.step === 'paid') {
         this.moveToStep('success');
+      } else { // Edge case where the transaction gets accelerated by someone else or on another session
+        this.closeModal();
       }
     }
   }
@@ -378,9 +381,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
    * Account-based acceleration request
    */
   accelerateWithMempoolAccount(): void {
-    if (!this.canPay || this.calculating) {
+    if (!this.canPay || this.calculating || this.processing) {
       return;
     }
+    this.processing = true;
     if (this.accelerationSubscription) {
       this.accelerationSubscription.unsubscribe();
     }
@@ -390,6 +394,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
       this.accelerationUUID
     ).subscribe({
       next: () => {
+        this.processing = false;
         this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
         this.audioService.playSound('ascend-chime-cartoon');
         this.showSuccess = true;
@@ -397,6 +402,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
         this.moveToStep('paid');
       },
       error: (response) => {
+        this.processing = false;
         this.accelerateError = response.error;
       }
     });
@@ -466,10 +472,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
    * APPLE PAY
    */
   async requestApplePayPayment(): Promise<void> {
+    if (this.processing) {
+      return;
+    }
     if (this.conversionsSubscription) {
       this.conversionsSubscription.unsubscribe();
     }
 
+    this.processing = true;
     this.conversionsSubscription = this.stateService.conversions$.subscribe(
       async (conversions) => {
         this.conversions = conversions;
@@ -494,6 +504,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
             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;
@@ -505,6 +516,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
               if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
                 console.error(`Cannot retreive 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());
@@ -516,6 +528,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
                 this.accelerationUUID
               ).subscribe({
                 next: () => {
+                  this.processing = false;
                   this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
                   this.audioService.playSound('ascend-chime-cartoon');
                   if (this.applePay) {
@@ -526,6 +539,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
                   }, 1000);
                 },
                 error: (response) => {
+                  this.processing = false;
                   this.accelerateError = response.error;
                   if (!(response.status === 403 && response.error === 'not_available')) {
                     setTimeout(() => {
@@ -537,6 +551,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
                 }
               });
             } else {
+              this.processing = false;
               let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
               if (tokenResult.errors) {
                 errorMessage += ` and errors: ${JSON.stringify(
@@ -547,6 +562,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
             }
           });
         } catch (e) {
+          this.processing = false;
           console.error(e);
         }
       }
@@ -557,10 +573,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
    * GOOGLE PAY
    */
   async requestGooglePayPayment(): Promise<void> {
+    if (this.processing) {
+      return;
+    }
     if (this.conversionsSubscription) {
       this.conversionsSubscription.unsubscribe();
     }
-
+    
+    this.processing = true;
     this.conversionsSubscription = this.stateService.conversions$.subscribe(
       async (conversions) => {
         this.conversions = conversions;
@@ -595,6 +615,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
             if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
               console.error(`Cannot retreive 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());
@@ -606,6 +627,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
               this.accelerationUUID
             ).subscribe({
               next: () => {
+                this.processing = false;
                 this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
                 this.audioService.playSound('ascend-chime-cartoon');
                 if (this.googlePay) {
@@ -616,6 +638,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
                 }, 1000);
               },
               error: (response) => {
+                this.processing = false;
                 this.accelerateError = response.error;
                 if (!(response.status === 403 && response.error === 'not_available')) {
                   setTimeout(() => {
@@ -627,6 +650,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
               }
             });
           } else {
+            this.processing = false;
             let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
             if (tokenResult.errors) {
               errorMessage += ` and errors: ${JSON.stringify(
@@ -644,10 +668,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
    * CASHAPP
    */
   async requestCashAppPayment(): Promise<void> {
+    if (this.processing) {
+      return;
+    }
     if (this.conversionsSubscription) {
       this.conversionsSubscription.unsubscribe();
     }
 
+    this.processing = true;
     this.conversionsSubscription = this.stateService.conversions$.subscribe(
       async (conversions) => {
         this.conversions = conversions;
@@ -678,6 +706,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
         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$(
@@ -688,6 +717,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
               this.accelerationUUID
             ).subscribe({
               next: () => {
+                this.processing = false;
                 this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
                 this.audioService.playSound('ascend-chime-cartoon');
                 if (this.cashAppPay) {
@@ -702,6 +732,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
                 }, 1000);
               },
               error: (response) => {
+                this.processing = false;
                 this.accelerateError = response.error;
                 if (!(response.status === 403 && response.error === 'not_available')) {
                   setTimeout(() => {
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.html
index a5e258210..564ee0ad1 100644
--- a/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.html
+++ b/frontend/src/app/components/accelerate-checkout/accelerate-fee-graph.component.html
@@ -12,7 +12,7 @@
           </p>
         </div>
         <div class="spacer"></div>
-        <span class="fee">{{ bar.class === 'tx' ? '' : '+' }}{{ bar.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></span>
+        <span class="fee">{{ bar.class === 'tx' ? '' : '+' }}{{ bar.fee | number }} <span class="symbol" i18n="shared.sats">sats</span></span>
         <div class="spacer"></div>
         <div class="spacer"></div>
       </div>
diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html
index 67f6cb80e..0f436f9ac 100644
--- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html
+++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html
@@ -21,14 +21,14 @@
       </tr>
       <tr *ngIf="accelerationInfo.fee">
         <td class="label" i18n="transaction.fee|Transaction fee">Fee</td>
-        <td class="value">{{ accelerationInfo.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td>
+        <td class="value">{{ accelerationInfo.fee | number }} <span class="symbol" i18n="shared.sats">sats</span></td>
       </tr>
       <tr *ngIf="accelerationInfo.bidBoost >= 0 || accelerationInfo.feeDelta">
         <td class="label" i18n="transaction.out-of-band-fees">Out-of-band fees</td>
         @if (accelerationInfo.status === 'accelerated') {
-          <td class="value oobFees">{{ accelerationInfo.feeDelta | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td>
+          <td class="value oobFees">{{ accelerationInfo.feeDelta | number }} <span class="symbol" i18n="shared.sats">sats</span></td>
         } @else {
-          <td class="value oobFees">{{ accelerationInfo.bidBoost | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td>
+          <td class="value oobFees">{{ accelerationInfo.bidBoost | number }} <span class="symbol" i18n="shared.sats">sats</span></td>
         }
       </tr>
       <tr *ngIf="accelerationInfo.fee && accelerationInfo.weight">
@@ -47,13 +47,14 @@
       <tr *ngIf="['accelerated', 'mined'].includes(accelerationInfo.status) && hasPoolsData()">
         <td class="label" i18n="transaction.accelerated-by-hashrate|Accelerated to hashrate">Accelerated by</td>
         <td class="value" *ngIf="accelerationInfo.pools">
-          <ng-container *ngFor="let pool of accelerationInfo.pools">
+          <ng-container *ngFor="let pool of accelerationInfo.pools; let i = index;">
             <img *ngIf="accelerationInfo.poolsData[pool]" 
               class="pool-logo" 
               [style.opacity]="accelerationInfo?.minedByPoolUniqueId && pool !== accelerationInfo?.minedByPoolUniqueId ? '0.3' : '1'"
               [src]="'/resources/mining-pools/' + accelerationInfo.poolsData[pool].slug + '.svg'" 
               onError="this.src = '/resources/mining-pools/default.svg'" 
               [alt]="'Logo of ' + pool.name + ' mining pool'">
+            <br *ngIf="i % 6 === 5">
           </ng-container>
         </td>
       </tr>
diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss
index 98a42f0e7..a8c4cd5cf 100644
--- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss
+++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss
@@ -23,6 +23,7 @@
 
   .label {
     padding-right: 30px;
+    vertical-align: top;
   }
 
   .pool-logo {
@@ -30,7 +31,8 @@
     height: 22px;
     position: relative;
     top: -1px;
-    margin-right: 3px;
+    margin-right: 4px;
+    margin-bottom: 4px;
   }
 
   .oobFees {
diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html
index 560e54629..ba0d44884 100644
--- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html
+++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html
@@ -38,7 +38,7 @@
         <div class="node-spacer"></div>
         <div class="interval">
           <div class="interval-time">
-            <app-time [time]="acceleratedAt - transactionTime"></app-time>
+            <app-time [time]="firstSeenToAccelerated"></app-time>
           </div>
         </div>
         <div class="node-spacer"></div>
@@ -46,7 +46,7 @@
           <div class="interval-time">
             @if (tx.status.confirmed) {
               <div class="interval-time">
-                <app-time [time]="tx.status.block_time - acceleratedAt"></app-time>
+                <app-time [time]="acceleratedToMined"></app-time>
               </div>
             } @else if (standardETA && !tx.status.confirmed) {
               <!-- ~<app-time [time]="standardETA / 1000 - now"></app-time> -->
diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts
index da0eee4a3..16fd24c7f 100644
--- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts
+++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts
@@ -24,6 +24,8 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
   accelerateRatio: number;
   useAbsoluteTime: boolean = false;
   interval: number;
+  firstSeenToAccelerated: number;
+  acceleratedToMined: number;
 
   tooltipPosition = null;
   hoverInfo: any = null;
@@ -35,8 +37,6 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
 
   ngOnInit(): void {
     this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000;
-    this.now = Math.floor(new Date().getTime() / 1000);
-    this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
 
     this.miningService.getPools().subscribe(pools => {
       for (const pool of pools) {
@@ -44,10 +44,8 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
       }
     });
 
-    this.interval = window.setInterval(() => {
-      this.now = Math.floor(new Date().getTime() / 1000);
-      this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
-    }, 60000);
+    this.updateTimes();
+    this.interval = window.setInterval(this.updateTimes.bind(this), 60000);
   }
 
   ngOnChanges(changes): void {
@@ -64,6 +62,13 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
     // }
   }
 
+  updateTimes(): void {
+    this.now = Math.floor(new Date().getTime() / 1000);
+    this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
+    this.firstSeenToAccelerated = Math.max(0, this.acceleratedAt - this.transactionTime);
+    this.acceleratedToMined = Math.max(0, this.tx.status.block_time - this.acceleratedAt);
+  }
+
   ngOnDestroy(): void {
     clearInterval(this.interval);
   }
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 d78b663a4..68a2bdd52 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
@@ -264,7 +264,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
           type: 'bar',
           barWidth: '90%',
           large: true,
-          barMinHeight: 1,
+          barMinHeight: 3,
         },
       ],
       dataZoom: (this.widget || data.length === 0 )? undefined : [{
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 8bdd4f14d..5ac288b2e 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
@@ -33,7 +33,7 @@
               <app-fee-rate [fee]="acceleration.effectiveFee" [weight]="acceleration.effectiveVsize * 4"></app-fee-rate>
             </td>
             <td class="bid text-right">
-              {{ (acceleration.feeDelta) | number }} <span class="symbol" i18n="shared.sat|sat">sat</span>
+              {{ (acceleration.feeDelta) | number }} <span class="symbol" i18n="shared.sats">sats</span>
             </td>
             <td class="time text-right">
               <app-time kind="since" [time]="acceleration.added" [fastRender]="true" [showTooltip]="true"></app-time>
@@ -41,7 +41,7 @@
           </ng-container>
           <ng-container *ngIf="!pending">
             <td *ngIf="acceleration.boost != null" class="fee text-right">
-              {{ acceleration.boost | number }} <span class="symbol" i18n="shared.sat|sat">sat</span>
+              {{ acceleration.boost | number }} <span class="symbol" i18n="shared.sats">sats</span>
             </td>
             <td *ngIf="acceleration.boost == null" class="fee text-right">
               ~
@@ -64,7 +64,7 @@
               <span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
               <span *ngIf="acceleration.status.includes('completed') && acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]" class="badge badge-success"><ng-container i18n="accelerator.completed">Completed</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;⌛</span></span>
               <span *ngIf="acceleration.status.includes('completed') && (!acceleration.minedByPoolUniqueId || !pools[acceleration.minedByPoolUniqueId])" class="badge badge-success"><ng-container i18n="transaction.rbf.mined">Mined</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;⌛</span></span>
-              <span *ngIf="acceleration.status.includes('failed')" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Failed</ng-container><span *ngIf="acceleration.status === 'failed_provisional'">&nbsp;⌛</span></span>
+              <span *ngIf="acceleration.status.includes('failed')" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Canceled</ng-container><span *ngIf="acceleration.status === 'failed_provisional'">&nbsp;⌛</span></span>
             </td>
             <td class="date text-right" *ngIf="!this.widget">
               <app-time kind="since" [time]="acceleration.added" [fastRender]="true" [showTooltip]="true"></app-time>
diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts
index e45a983e1..a334f096a 100644
--- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts
+++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts
@@ -1,5 +1,5 @@
 import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy, Inject, LOCALE_ID } from '@angular/core';
-import { BehaviorSubject, Observable, Subscription, catchError, filter, of, switchMap, tap, throttleTime } from 'rxjs';
+import { BehaviorSubject, Observable, Subscription, catchError, combineLatest, filter, of, switchMap, tap, throttleTime, timer } from 'rxjs';
 import { Acceleration, BlockExtended, SinglePoolStats } from '../../../interfaces/node-api.interface';
 import { StateService } from '../../../services/state.service';
 import { WebsocketService } from '../../../services/websocket.service';
@@ -61,8 +61,11 @@ export class AccelerationsListComponent implements OnInit, OnDestroy {
       this.websocketService.want(['blocks']);
       this.seoService.setTitle($localize`:@@02573b6980a2d611b4361a2595a4447e390058cd:Accelerations`);
 
-      this.paramSubscription = this.route.params.pipe(
-        tap(params => {
+      this.paramSubscription = combineLatest([
+        this.route.params,
+        timer(0),
+      ]).pipe(
+        tap(([params]) => {
           this.page = +params['page'] || 1;
           this.pageSubject.next(this.page);
         })
diff --git a/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.html b/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.html
index 13d38443e..dbc79fb95 100644
--- a/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.html
+++ b/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.html
@@ -10,10 +10,10 @@
       </td>
       <td class="field-value" [class]="chartPositionLeft ? 'chart-left' : ''">
         <div class="effective-fee-container">
-          @if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize)) {
+          @if (accelerationInfo?.acceleratedFeeRate && (!effectiveFeeRate || accelerationInfo.acceleratedFeeRate >= effectiveFeeRate)) {
             <app-fee-rate class="oobFees" [fee]="accelerationInfo.acceleratedFeeRate"></app-fee-rate>
           } @else {
-            <app-fee-rate class="oobFees" [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
+            <app-fee-rate class="oobFees" [fee]="effectiveFeeRate"></app-fee-rate>
           }
         </div>
       </td>
diff --git a/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.ts b/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.ts
index 7506fb6fc..fb727c1a4 100644
--- a/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.ts
+++ b/frontend/src/app/components/acceleration/active-acceleration-box/active-acceleration-box.component.ts
@@ -1,4 +1,4 @@
-import { Component, ChangeDetectionStrategy, Input, Output, OnChanges, SimpleChanges, EventEmitter } from '@angular/core';
+import { Component, ChangeDetectionStrategy, Input, Output, OnChanges, SimpleChanges, EventEmitter, ChangeDetectorRef } from '@angular/core';
 import { Transaction } from '../../../interfaces/electrs.interface';
 import { Acceleration, SinglePoolStats } from '../../../interfaces/node-api.interface';
 import { EChartsOption, PieSeriesOption } from '../../../graphs/echarts';
@@ -23,7 +23,8 @@ function toRGB({r,g,b}): string {
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class ActiveAccelerationBox implements OnChanges {
-  @Input() tx: Transaction;
+  @Input() acceleratedBy?: number[];
+  @Input() effectiveFeeRate?: number;
   @Input() accelerationInfo: Acceleration;
   @Input() miningStats: MiningStats;
   @Input() pools: number[];
@@ -41,10 +42,12 @@ export class ActiveAccelerationBox implements OnChanges {
   timespan = '';
   chartInstance: any = undefined;
 
-  constructor() {}
+  constructor(
+    private cd: ChangeDetectorRef,
+  ) {}
 
   ngOnChanges(changes: SimpleChanges): void {
-    const pools = this.pools || this.accelerationInfo?.pools || this.tx.acceleratedBy;
+    const pools = this.pools || this.accelerationInfo?.pools || this.acceleratedBy;
     if (pools && this.miningStats) {
       this.prepareChartOptions(pools);
     }
@@ -67,13 +70,17 @@ export class ActiveAccelerationBox implements OnChanges {
 
     const acceleratingPools = (poolList || []).filter(id => pools[id]).sort((a,b) => pools[a].lastEstimatedHashrate - pools[b].lastEstimatedHashrate);
     const totalAcceleratedHashrate = acceleratingPools.reduce((total, pool) => total + pools[pool].lastEstimatedHashrate, 0);
-    const lightenStep = acceleratingPools.length ? (0.48 / acceleratingPools.length) : 0;
+    // Find the first pool with at least 1% of the total network hashrate
+    const firstSignificantPool = acceleratingPools.findIndex(pool => pools[pool].lastEstimatedHashrate > this.miningStats.lastEstimatedHashrate / 100);
+    const numSignificantPools = acceleratingPools.length - firstSignificantPool;
     acceleratingPools.forEach((poolId, index) => {
       const pool = pools[poolId];
       const poolShare = ((pool.lastEstimatedHashrate / this.miningStats.lastEstimatedHashrate) * 100).toFixed(1);
       data.push(getDataItem(
         pool.lastEstimatedHashrate,
-        toRGB(lighten({ r: 147, g: 57, b: 244 }, index * lightenStep)),
+        index >= firstSignificantPool
+          ? toRGB(lighten({ r: 147, g: 57, b: 244 }, 1 - (index - firstSignificantPool) / (numSignificantPools - 1)))
+          : 'white',
         `<b style="color: white">${pool.name} (${poolShare}%)</b>`,
         true,
       ) as PieSeriesOption);
@@ -128,6 +135,7 @@ export class ActiveAccelerationBox implements OnChanges {
         }
       ]
     };
+    this.cd.markForCheck();
   }
 
   onChartInit(ec) {
diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts
index dd81b9809..ff3c27240 100644
--- a/frontend/src/app/components/address-labels/address-labels.component.ts
+++ b/frontend/src/app/components/address-labels/address-labels.component.ts
@@ -55,7 +55,7 @@ export class AddressLabelsComponent implements OnChanges {
   }
 
   handleVin() {
-    const address = new AddressTypeInfo(this.network || 'mainnet', this.vin.prevout?.scriptpubkey_address, this.vin.prevout?.scriptpubkey_type as AddressType, [this.vin])
+    const address = new AddressTypeInfo(this.network || 'mainnet', this.vin.prevout?.scriptpubkey_address, this.vin.prevout?.scriptpubkey_type as AddressType, [this.vin]);
     if (address?.scripts.size) {
       const script = address?.scripts.values().next().value;
       if (script.template?.label) {
diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html
index 31dff2fa5..b893d7e22 100644
--- a/frontend/src/app/components/address/address.component.html
+++ b/frontend/src/app/components/address/address.component.html
@@ -94,6 +94,20 @@
       </div>
     </ng-container>
 
+    <ng-container *ngIf="(stateService.backend$ | async) === 'esplora' && address && utxos && utxos.length > 2">
+      <br>
+      <div class="title-tx">
+        <h2 class="text-left" i18n="address.unspent-outputs">Unspent Outputs</h2>
+      </div>
+      <div class="box">
+        <div class="row">
+          <div class="col-md">
+            <app-utxo-graph [utxos]="utxos" left="80" />
+          </div>
+        </div>
+      </div>
+    </ng-container>
+
     <br>
     <div class="title-tx">
       <h2 class="text-left">
diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts
index 105863a4e..57818ea33 100644
--- a/frontend/src/app/components/address/address.component.ts
+++ b/frontend/src/app/components/address/address.component.ts
@@ -2,12 +2,12 @@ import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
 import { ActivatedRoute, ParamMap } from '@angular/router';
 import { ElectrsApiService } from '../../services/electrs-api.service';
 import { switchMap, filter, catchError, map, tap } from 'rxjs/operators';
-import { Address, ChainStats, Transaction, Vin } from '../../interfaces/electrs.interface';
+import { Address, ChainStats, Transaction, Utxo, Vin } from '../../interfaces/electrs.interface';
 import { WebsocketService } from '../../services/websocket.service';
 import { StateService } from '../../services/state.service';
 import { AudioService } from '../../services/audio.service';
 import { ApiService } from '../../services/api.service';
-import { of, merge, Subscription, Observable } from 'rxjs';
+import { of, merge, Subscription, Observable, forkJoin } from 'rxjs';
 import { SeoService } from '../../services/seo.service';
 import { seoDescriptionNetwork } from '../../shared/common.utils';
 import { AddressInformation } from '../../interfaces/node-api.interface';
@@ -104,6 +104,7 @@ export class AddressComponent implements OnInit, OnDestroy {
   addressString: string;
   isLoadingAddress = true;
   transactions: Transaction[];
+  utxos: Utxo[];
   isLoadingTransactions = true;
   retryLoadMore = false;
   error: any;
@@ -159,6 +160,7 @@ export class AddressComponent implements OnInit, OnDestroy {
           this.address = null;
           this.isLoadingTransactions = true;
           this.transactions = null;
+          this.utxos = null;
           this.addressInfo = null;
           this.exampleChannel = null;
           document.body.scrollTo(0, 0);
@@ -212,11 +214,23 @@ export class AddressComponent implements OnInit, OnDestroy {
           this.updateChainStats();
           this.isLoadingAddress = false;
           this.isLoadingTransactions = true;
-          return address.is_pubkey
+          const utxoCount = this.chainStats.utxos + this.mempoolStats.utxos;
+          return forkJoin([
+            address.is_pubkey
               ? this.electrsApiService.getScriptHashTransactions$((address.address.length === 66 ? '21' : '41') + address.address + 'ac')
-              : this.electrsApiService.getAddressTransactions$(address.address);
+              : this.electrsApiService.getAddressTransactions$(address.address),
+            (utxoCount > 2 && utxoCount <= 500 ? (address.is_pubkey
+              ? this.electrsApiService.getScriptHashUtxos$((address.address.length === 66 ? '21' : '41') + address.address + 'ac')
+              : this.electrsApiService.getAddressUtxos$(address.address)) : of(null)).pipe(
+                catchError(() => {
+                  return of(null);
+                })
+              )
+          ]);
         }),
-        switchMap((transactions) => {
+        switchMap(([transactions, utxos]) => {
+          this.utxos = utxos;
+
           this.tempTransactions = transactions;
           if (transactions.length) {
             this.lastTransactionTxId = transactions[transactions.length - 1].txid;
@@ -309,6 +323,7 @@ export class AddressComponent implements OnInit, OnDestroy {
           this.transactions = this.transactions.slice();
           this.mempoolStats.removeTx(transaction);
           this.audioService.playSound('magic');
+          this.confirmTransaction(tx);
         } else {
           if (this.addTransaction(transaction, false)) {
             this.audioService.playSound('magic');
@@ -334,6 +349,31 @@ export class AddressComponent implements OnInit, OnDestroy {
       }
     }
 
+    // update utxos in-place
+    if (this.utxos != null) {
+      let utxosChanged = false;
+      for (const vin of transaction.vin) {
+        const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === vin.txid && utxo.vout === vin.vout);
+        if (utxoIndex !== -1) {
+          this.utxos.splice(utxoIndex, 1);
+          utxosChanged = true;
+        }
+      }
+      for (const [index, vout] of transaction.vout.entries()) {
+        if (vout.scriptpubkey_address === this.address.address) {
+          this.utxos.push({
+            txid: transaction.txid,
+            vout: index,
+            value: vout.value,
+            status: JSON.parse(JSON.stringify(transaction.status)),
+          });
+          utxosChanged = true;
+        }
+      }
+      if (utxosChanged) {
+        this.utxos = this.utxos.slice();
+      }
+    }
     return true;
   }
 
@@ -346,9 +386,65 @@ export class AddressComponent implements OnInit, OnDestroy {
     this.transactions.splice(index, 1);
     this.transactions = this.transactions.slice();
 
+    // update utxos in-place
+    if (this.utxos != null) {
+      let utxosChanged = false;
+      for (const vin of transaction.vin) {
+        if (vin.prevout?.scriptpubkey_address === this.address.address) {
+          this.utxos.push({
+            txid: vin.txid,
+            vout: vin.vout,
+            value: vin.prevout.value,
+            status: { confirmed: true }, // Assuming the input was confirmed
+          });
+          utxosChanged = true;
+        }
+      }
+      for (const [index, vout] of transaction.vout.entries()) {
+        if (vout.scriptpubkey_address === this.address.address) {
+          const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === transaction.txid && utxo.vout === index);
+          if (utxoIndex !== -1) {
+            this.utxos.splice(utxoIndex, 1);
+            utxosChanged = true;
+          }
+        }
+      }
+      if (utxosChanged) {
+        this.utxos = this.utxos.slice();
+      }
+    }
+
     return true;
   }
 
+  confirmTransaction(transaction: Transaction): void {
+    // update utxos in-place
+    if (this.utxos != null) {
+      let utxosChanged = false;
+      for (const vin of transaction.vin) {
+        if (vin.prevout?.scriptpubkey_address === this.address.address) {
+          const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === vin.txid && utxo.vout === vin.vout);
+          if (utxoIndex !== -1) {
+            this.utxos[utxoIndex].status = JSON.parse(JSON.stringify(transaction.status));
+            utxosChanged = true;
+          }
+        }
+      }
+      for (const [index, vout] of transaction.vout.entries()) {
+        if (vout.scriptpubkey_address === this.address.address) {
+          const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === transaction.txid && utxo.vout === index);
+          if (utxoIndex !== -1) {
+            this.utxos[utxoIndex].status = JSON.parse(JSON.stringify(transaction.status));
+            utxosChanged = true;
+          }
+        }
+      }
+      if (utxosChanged) {
+        this.utxos = this.utxos.slice();
+      }
+    }
+  }
+
   loadMore(): void {
     if (this.isLoadingTransactions || this.fullyLoaded) {
       return;
diff --git a/frontend/src/app/components/amount-selector/amount-selector.component.html b/frontend/src/app/components/amount-selector/amount-selector.component.html
new file mode 100644
index 000000000..a16a24d4f
--- /dev/null
+++ b/frontend/src/app/components/amount-selector/amount-selector.component.html
@@ -0,0 +1,7 @@
+<div [formGroup]="amountForm" class="text-small text-center">
+    <select formControlName="mode" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 70px;" (change)="changeMode()">
+        <option value="btc" i18n="shared.btc|BTC">BTC</option>
+        <option value="sats" i18n="shared.sats">sats</option>
+        <option value="fiat" i18n="shared.fiat|Fiat">Fiat</option>
+    </select>
+</div>
diff --git a/frontend/src/app/components/amount-selector/amount-selector.component.scss b/frontend/src/app/components/amount-selector/amount-selector.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/src/app/components/amount-selector/amount-selector.component.ts b/frontend/src/app/components/amount-selector/amount-selector.component.ts
new file mode 100644
index 000000000..144b0f1db
--- /dev/null
+++ b/frontend/src/app/components/amount-selector/amount-selector.component.ts
@@ -0,0 +1,36 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
+import { StorageService } from '../../services/storage.service';
+import { StateService } from '../../services/state.service';
+
+@Component({
+  selector: 'app-amount-selector',
+  templateUrl: './amount-selector.component.html',
+  styleUrls: ['./amount-selector.component.scss'],
+  changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class AmountSelectorComponent implements OnInit {
+  amountForm: UntypedFormGroup;
+  modes = ['btc', 'sats', 'fiat'];
+
+  constructor(
+    private formBuilder: UntypedFormBuilder,
+    private stateService: StateService,
+    private storageService: StorageService,
+  ) { }
+
+  ngOnInit() {
+    this.amountForm = this.formBuilder.group({
+      mode: ['btc']
+    });
+    this.stateService.viewAmountMode$.subscribe((mode) => {
+      this.amountForm.get('mode')?.setValue(mode);
+    });
+  }
+
+  changeMode() {
+    const newMode = this.amountForm.get('mode')?.value;
+    this.storageService.setValue('view-amount-mode', newMode);
+    this.stateService.viewAmountMode$.next(newMode);
+  }
+}
diff --git a/frontend/src/app/components/amount/amount.component.html b/frontend/src/app/components/amount/amount.component.html
index b513c89d2..cbbdb2dd9 100644
--- a/frontend/src/app/components/amount/amount.component.html
+++ b/frontend/src/app/components/amount/amount.component.html
@@ -30,7 +30,7 @@
       @if (digitsInfo === '1.8-8') {
         &lrm;{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number }}
       } @else { 
-        &lrm;{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | amountShortener : satoshis < 1000 && satoshis > -1000 ? 0 : 1 }}
+        &lrm;{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | amountShortener : (satoshis < 1000 && satoshis > -1000 ? 0 : 1) : undefined : true }}
       }
       <span class="symbol">
         <ng-container *ngTemplateOutlet="prefix"></ng-container>sats
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts
index ab9a29293..3be0692a5 100644
--- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts
+++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts
@@ -198,7 +198,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
   }
 
   // initialize the scene without any entry transition
-  setup(transactions: TransactionStripped[]): void {
+  setup(transactions: TransactionStripped[], sort: boolean = false): void {
     const filtersAvailable = transactions.reduce((flagSet, tx) => flagSet || tx.flags > 0, false);
     if (filtersAvailable !== this.filtersAvailable) {
       this.setFilterFlags();
@@ -206,7 +206,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
     this.filtersAvailable = filtersAvailable;
     if (this.scene) {
       this.clearUpdateQueue();
-      this.scene.setup(transactions);
+      this.scene.setup(transactions, sort);
       this.readyNextFrame = true;
       this.start();
       this.updateSearchHighlight();
diff --git a/frontend/src/app/components/block-overview-graph/block-scene.ts b/frontend/src/app/components/block-overview-graph/block-scene.ts
index c59fcb7d4..4f07818a5 100644
--- a/frontend/src/app/components/block-overview-graph/block-scene.ts
+++ b/frontend/src/app/components/block-overview-graph/block-scene.ts
@@ -88,16 +88,19 @@ export default class BlockScene {
   }
 
   // set up the scene with an initial set of transactions, without any transition animation
-  setup(txs: TransactionStripped[]) {
+  setup(txs: TransactionStripped[], sort: boolean = false) {
     // clean up any old transactions
     Object.values(this.txs).forEach(tx => {
       tx.destroy();
       delete this.txs[tx.txid];
     });
     this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight });
-    txs.forEach(tx => {
-      const txView = new TxView(tx, this);
-      this.txs[tx.txid] = txView;
+    let txViews = txs.map(tx => new TxView(tx, this));
+    if (sort) {
+      txViews = txViews.sort(feeRateDescending);
+    }
+    txViews.forEach(txView => {
+      this.txs[txView.txid] = txView;
       this.place(txView);
       this.saveGridToScreenPosition(txView);
       this.applyTxUpdate(txView, {
diff --git a/frontend/src/app/components/block-overview-graph/tx-view.ts b/frontend/src/app/components/block-overview-graph/tx-view.ts
index ad24b26c3..f612368f4 100644
--- a/frontend/src/app/components/block-overview-graph/tx-view.ts
+++ b/frontend/src/app/components/block-overview-graph/tx-view.ts
@@ -33,7 +33,7 @@ export default class TxView implements TransactionStripped {
   flags: number;
   bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n;
   time?: number;
-  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'added_prioritized' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
+  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'added_prioritized' | 'prioritized' | 'added_deprioritized' | 'deprioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
   context?: 'projected' | 'actual';
   scene?: BlockScene;
 
diff --git a/frontend/src/app/components/block-overview-graph/utils.ts b/frontend/src/app/components/block-overview-graph/utils.ts
index 4f7c7ed5a..287c4bf34 100644
--- a/frontend/src/app/components/block-overview-graph/utils.ts
+++ b/frontend/src/app/components/block-overview-graph/utils.ts
@@ -11,6 +11,10 @@ export function hexToColor(hex: string): Color {
   };
 }
 
+export function colorToHex(color: Color): string {
+  return [color.r, color.g, color.b].map(c => Math.round(c * 255).toString(16)).join('');
+}
+
 export function desaturate(color: Color, amount: number): Color {
   const gray = (color.r + color.g + color.b) / 6;
   return {
@@ -30,6 +34,15 @@ export function darken(color: Color, amount: number): Color {
   };
 }
 
+export function mix(color1: Color, color2: Color, amount: number): Color {
+  return {
+    r: color1.r * (1 - amount) + color2.r * amount,
+    g: color1.g * (1 - amount) + color2.g * amount,
+    b: color1.b * (1 - amount) + color2.b * amount,
+    a: color1.a * (1 - amount) + color2.a * amount,
+  };
+}
+
 export function setOpacity(color: Color, opacity: number): Color {
   return {
     ...color,
@@ -142,6 +155,10 @@ export function defaultColorFunction(
       return auditColors.added_prioritized;
     case 'prioritized':
       return auditColors.prioritized;
+    case 'added_deprioritized':
+      return auditColors.added_prioritized;
+    case 'deprioritized':
+      return auditColors.prioritized;
     case 'selected':
       return colors.marginal[levelIndex] || colors.marginal[defaultMempoolFeeColors.length - 1];
     case 'accelerated':
diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
index 037229398..f8fb3c89d 100644
--- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
+++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
@@ -40,7 +40,7 @@
       </tr>
       <tr>
         <td class="label" i18n="transaction.fee|Transaction fee">Fee</td>
-        <td class="value">{{ fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> &nbsp; <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="fee"></app-fiat></span>
+        <td class="value">{{ fee | number }} <span class="symbol" i18n="shared.sats">sats</span> &nbsp; <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="fee"></app-fiat></span>
       </tr>
       <tr>
         <td class="label" i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
@@ -79,6 +79,11 @@
               <span class="badge badge-warning" i18n="tx-features.tag.added|Added">Added</span>
               <span class="badge badge-warning ml-1" i18n="tx-features.tag.prioritized|Prioritized">Prioritized</span>
             </ng-container>
+            <span *ngSwitchCase="'deprioritized'" class="badge badge-warning" i18n="tx-features.tag.prioritized|Deprioritized">Deprioritized</span>
+            <ng-container *ngSwitchCase="'added_deprioritized'">
+              <span class="badge badge-warning" i18n="tx-features.tag.added|Added">Added</span>
+              <span class="badge badge-warning ml-1" i18n="tx-features.tag.prioritized|Deprioritized">Deprioritized</span>
+            </ng-container>
             <span *ngSwitchCase="'selected'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
             <span *ngSwitchCase="'rbf'" class="badge badge-warning" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
             <span *ngSwitchCase="'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
diff --git a/frontend/src/app/components/block/block-preview.component.html b/frontend/src/app/components/block/block-preview.component.html
index 56fa8886e..036ab8399 100644
--- a/frontend/src/app/components/block/block-preview.component.html
+++ b/frontend/src/app/components/block/block-preview.component.html
@@ -53,6 +53,13 @@
             <td i18n="block.miner">Miner</td>
             <td *ngIf="stateService.env.MINING_DASHBOARD">
               <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
+                <span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
+                  @if (block.extras.pool.minerNames[1].length > 16) {
+                    {{ block.extras.pool.minerNames[1].slice(0, 15) }}…
+                  } @else {
+                    {{ block.extras.pool.minerNames[1] }}
+                  }
+                </span>
                 <img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
                 {{ block.extras.pool.name }}
               </a>
@@ -60,8 +67,15 @@
             <td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
               <span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
                 [class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
-                {{ block?.extras.pool.name }}
-            </span>
+                <span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
+                  @if (block.extras.pool.minerNames[1].length > 16) {
+                    {{ block.extras.pool.minerNames[1].slice(0, 15) }}…
+                  } @else {
+                    {{ block.extras.pool.minerNames[1] }}
+                  }
+                </span>
+                {{ block.extras.pool.name }}
+              </span>
             </td>
           </tr>
         </tbody>
diff --git a/frontend/src/app/components/block/block-preview.component.ts b/frontend/src/app/components/block/block-preview.component.ts
index 72da96818..572f91a38 100644
--- a/frontend/src/app/components/block/block-preview.component.ts
+++ b/frontend/src/app/components/block/block-preview.component.ts
@@ -137,7 +137,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
                 })
               ),
             this.stateService.env.ACCELERATOR === true && block.height > 819500
-              ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height })
+              ? this.servicesApiService.getAllAccelerationHistory$({ blockHeight: block.height })
                 .pipe(catchError(() => {
                   return of([]);
                 }))
diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html
index 1dd9d8a8d..105cdf31a 100644
--- a/frontend/src/app/components/block/block.component.html
+++ b/frontend/src/app/components/block/block.component.html
@@ -66,10 +66,10 @@
                     [class.badge-success]="blockAudit?.matchRate >= 99"
                     [class.badge-warning]="blockAudit?.matchRate >= 75 && blockAudit?.matchRate < 99"
                     [class.badge-danger]="blockAudit?.matchRate < 75"
-                    *ngIf="blockAudit?.matchRate != null; else nullHealth"
+                    *ngIf="blockAudit?.matchRate != null && blockAudit?.id === block.id; else nullHealth"
                   >{{ blockAudit?.matchRate }}%</span>
                   <ng-template #nullHealth>
-                    <ng-container *ngIf="!isLoadingOverview; else loadingHealth">
+                    <ng-container *ngIf="!isLoadingOverview && blockAudit?.id === block.id; else loadingHealth">
                       <span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
                     </ng-container>
                   </ng-template>
@@ -182,6 +182,13 @@
         <td i18n="block.miner">Miner</td>
         <td *ngIf="stateService.env.MINING_DASHBOARD">
           <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
+            <span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
+              @if (block.extras.pool.minerNames[1].length > 16) {
+                {{ block.extras.pool.minerNames[1].slice(0, 15) }}…
+              } @else {
+                {{ block.extras.pool.minerNames[1] }}
+              }
+            </span>
             <img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
             {{ block.extras.pool.name }}
           </a>
diff --git a/frontend/src/app/components/block/block.component.scss b/frontend/src/app/components/block/block.component.scss
index fe5318375..945d61366 100644
--- a/frontend/src/app/components/block/block.component.scss
+++ b/frontend/src/app/components/block/block.component.scss
@@ -81,6 +81,19 @@ h1 {
   }
 }
 
+.miner-name {
+  margin-right: 4px;
+  vertical-align: top;
+}
+
+.pool-logo {
+  width: 25px;
+  height: 25px;
+  position: relative;
+  top: -1px;
+  margin-right: 2px;
+}
+
 .row {
 	flex-direction: column;
 	@media (min-width: 768px) {
diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts
index 44328c591..baf583744 100644
--- a/frontend/src/app/components/block/block.component.ts
+++ b/frontend/src/app/components/block/block.component.ts
@@ -17,6 +17,7 @@ import { PriceService, Price } from '../../services/price.service';
 import { CacheService } from '../../services/cache.service';
 import { ServicesApiServices } from '../../services/services-api.service';
 import { PreloadService } from '../../services/preload.service';
+import { identifyPrioritizedTransactions } from '../../shared/transaction.utils';
 
 @Component({
   selector: 'app-block',
@@ -318,7 +319,7 @@ export class BlockComponent implements OnInit, OnDestroy {
     this.accelerationsSubscription = this.block$.pipe(
       switchMap((block) => {
         return this.stateService.env.ACCELERATOR === true && block.height > 819500
-          ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height })
+          ? this.servicesApiService.getAllAccelerationHistory$({ blockHeight: block.height })
             .pipe(catchError(() => {
               return of([]);
             }))
@@ -326,7 +327,7 @@ export class BlockComponent implements OnInit, OnDestroy {
       })
     ).subscribe((accelerations) => {
       this.accelerations = accelerations;
-      if (accelerations.length) {
+      if (accelerations.length && this.strippedTransactions) { // Don't call setupBlockAudit if we don't have transactions yet; it will be called later in overviewSubscription
         this.setupBlockAudit();
       }
     });
@@ -524,6 +525,7 @@ export class BlockComponent implements OnInit, OnDestroy {
       const isUnseen = {};
       const isAdded = {};
       const isPrioritized = {};
+      const isDeprioritized = {};
       const isCensored = {};
       const isMissing = {};
       const isSelected = {};
@@ -535,6 +537,17 @@ export class BlockComponent implements OnInit, OnDestroy {
       this.numUnexpected = 0;
 
       if (blockAudit?.template) {
+        // augment with locally calculated *de*prioritized transactions if possible
+        const { prioritized, deprioritized } = identifyPrioritizedTransactions(transactions);
+        // but if the local calculation produces returns unexpected results, don't use it
+        let useLocalDeprioritized = deprioritized.length < (transactions.length * 0.1);
+        for (const tx of prioritized) {
+          if (!isPrioritized[tx] && !isAccelerated[tx]) {
+            useLocalDeprioritized = false;
+            break;
+          }
+        }
+
         for (const tx of blockAudit.template) {
           inTemplate[tx.txid] = true;
           if (tx.acc) {
@@ -550,9 +563,14 @@ export class BlockComponent implements OnInit, OnDestroy {
         for (const txid of blockAudit.addedTxs) {
           isAdded[txid] = true;
         }
-        for (const txid of blockAudit.prioritizedTxs || []) {
+        for (const txid of blockAudit.prioritizedTxs) {
           isPrioritized[txid] = true;
         }
+        if (useLocalDeprioritized) {
+          for (const txid of deprioritized || []) {
+            isDeprioritized[txid] = true;
+          }
+        }
         for (const txid of blockAudit.missingTxs) {
           isCensored[txid] = true;
         }
@@ -608,6 +626,12 @@ export class BlockComponent implements OnInit, OnDestroy {
             } else {
               tx.status = 'prioritized';
             }
+          } else if (isDeprioritized[tx.txid]) {
+            if (isAdded[tx.txid] || (blockAudit.version > 0 && isUnseen[tx.txid])) {
+              tx.status = 'added_deprioritized';
+            } else {
+              tx.status = 'deprioritized';
+            }
           } else if (isAdded[tx.txid] && (blockAudit.version === 0 || isUnseen[tx.txid])) {
             tx.status = 'added';
           } else if (inTemplate[tx.txid]) {
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 a60e1db0a..a782e9588 100644
--- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html
+++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html
@@ -60,9 +60,14 @@
           </ng-container>
         </div>
         <div class="animated" *ngIf="block.extras?.pool != undefined && showPools">
-          <a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
-            <img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'"> 
-            {{ block.extras.pool.name}}
+          <a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge" [class.miner-name]="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
+            <ng-container *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''; else centralisedPool">
+              <img [ngbTooltip]="block.extras.pool.name" class="pool-logo faded" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
+              {{ block.extras.pool.minerNames[1] }}
+            </ng-container>
+            <ng-template #centralisedPool>
+              <img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'"> {{ block.extras.pool.name }}
+            </ng-template>
           </a>
         </div>
       </div>
diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss
index b8de4f2ca..5c2a5ab5a 100644
--- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss
+++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss
@@ -19,6 +19,38 @@
   pointer-events: none;
 }
 
+.on-pool-name-text {
+  display: inline-block;
+  padding-top: 2px;
+  font-weight: normal;
+}
+
+
+.on-pool {
+  align-items: center;
+  background-color: var(--bg);
+  display: inline-block;
+  margin-top: 4px;
+  padding: .25em .4em;
+  border-radius: .25rem;
+}
+
+.on-pool-container {
+  align-items: center;
+  position: relative;
+  top: -8px;
+  display: flex;
+  flex-direction: column;
+}
+
+.on-pool-container.selected {
+  top: 0px;
+}
+
+.pool-container {
+  margin-top: 12px;
+}
+
 .mined-block {
   position: absolute;
   top: 0px;
@@ -155,9 +187,16 @@
 
 .badge {
   position: relative;
-  top: 15px;
+  top: 19px;
   z-index: 101;
   color: #FFF;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 145px;
+
+  &.miner-name {
+    max-width: 125px;
+  }
 }
 
 .pool-logo {
@@ -168,6 +207,10 @@
   margin-right: 2px;
 }
 
+.pool-logo.faded {
+  filter: grayscale(100%) brightness(1.5);
+}
+
 .animated {
   transition: all 0.15s ease-in-out;
   white-space: nowrap;
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 d82472492..807d429bf 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.html
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.html
@@ -1,8 +1,11 @@
 <app-indexing-progress *ngIf="!widget"></app-indexing-progress>
 
 <div class="container-xl" style="min-height: 335px" [ngClass]="{'widget': widget, 'full-height': !widget, 'legacy': !isMempoolModule}">
-  <h1 *ngIf="!widget" class="float-left" i18n="master-page.blocks">Blocks</h1>
-  <div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
+  <div *ngIf="!widget" class="float-left" style="display: flex; width: 100%; align-items: center;">
+    <h1 i18n="master-page.blocks">Blocks</h1>
+    <app-svg-images name="blocks-2-3" style="width: 275px; max-width: 90%; margin-top: -10px"></app-svg-images>
+    <div *ngIf="!widget && isLoading" class="spinner-border" role="status"></div>
+  </div>
 
   <div class="clearfix"></div>
 
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss
index 2315844ae..9e4465cf1 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.scss
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss
@@ -1,7 +1,9 @@
 .spinner-border {
   height: 25px;
   width: 25px;
-  margin-top: 13px;
+  margin-top: -10px;
+  margin-left: -13px;
+  flex-shrink: 0;
 }
 
 .container-xl {
diff --git a/frontend/src/app/components/calculator/calculator.component.html b/frontend/src/app/components/calculator/calculator.component.html
index e4ade67d2..e205479ee 100644
--- a/frontend/src/app/components/calculator/calculator.component.html
+++ b/frontend/src/app/components/calculator/calculator.component.html
@@ -12,7 +12,7 @@
           <div class="input-group-prepend">
             <span class="input-group-text">{{ currency$ | async }}</span>
           </div>
-          <input type="text" class="form-control" formControlName="fiat" (input)="transformInput('fiat')" (click)="selectAll($event)">
+          <input type="text" inputmode="numeric" class="form-control" formControlName="fiat" (input)="transformInput('fiat')" (click)="selectAll($event)">
           <app-clipboard [button]="true" [text]="form.get('fiat').value" [class]="'btn btn-lg btn-secondary ml-1'"></app-clipboard>
         </div>
 
@@ -20,7 +20,7 @@
           <div class="input-group-prepend">
             <span class="input-group-text">BTC</span>
           </div>
-          <input type="text" class="form-control" formControlName="bitcoin" (input)="transformInput('bitcoin')" (click)="selectAll($event)">
+          <input type="text" inputmode="numeric" class="form-control" formControlName="bitcoin" (input)="transformInput('bitcoin')" (click)="selectAll($event)">
           <app-clipboard [button]="true" [text]="form.get('bitcoin').value" [class]="'btn btn-lg btn-secondary ml-1'"></app-clipboard>
         </div>
 
@@ -28,7 +28,7 @@
           <div class="input-group-prepend">
             <span class="input-group-text" i18n="shared.sats">sats</span>
           </div>
-          <input type="text" class="form-control" formControlName="satoshis" (input)="transformInput('satoshis')" (click)="selectAll($event)">
+          <input type="text" inputmode="numeric" class="form-control" formControlName="satoshis" (input)="transformInput('satoshis')" (click)="selectAll($event)">
           <app-clipboard [button]="true" [text]="form.get('satoshis').value" [class]="'btn btn-lg btn-secondary ml-1'"></app-clipboard>
         </div>
       </form>
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts
index 90b41d749..e19f510b5 100644
--- a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts
+++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts
@@ -77,7 +77,7 @@ export class DifficultyMiningComponent implements OnInit {
           base: `${da.progressPercent.toFixed(2)}%`,
           change: da.difficultyChange,
           progress: da.progressPercent,
-          remainingBlocks: da.remainingBlocks - 1,
+          remainingBlocks: da.remainingBlocks,
           colorAdjustments,
           colorPreviousAdjustments,
           newDifficultyHeight: da.nextRetargetHeight,
diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts
index 579b49fc3..6a99aecef 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.ts
+++ b/frontend/src/app/components/difficulty/difficulty.component.ts
@@ -153,8 +153,8 @@ export class DifficultyComponent implements OnInit {
           base: `${da.progressPercent.toFixed(2)}%`,
           change: da.difficultyChange,
           progress: da.progressPercent,
-          minedBlocks: this.currentIndex + 1,
-          remainingBlocks: da.remainingBlocks - 1,
+          minedBlocks: this.currentIndex,
+          remainingBlocks: da.remainingBlocks,
           expectedBlocks: Math.floor(da.expectedBlocks),
           colorAdjustments,
           colorPreviousAdjustments,
diff --git a/frontend/src/app/components/faucet/faucet.component.html b/frontend/src/app/components/faucet/faucet.component.html
index 89e6bb8a8..0f0307e54 100644
--- a/frontend/src/app/components/faucet/faucet.component.html
+++ b/frontend/src/app/components/faucet/faucet.component.html
@@ -5,7 +5,7 @@
   </div>
 
   <div class="faucet-container text-center">
-  
+
     @if (txid) {
       <div class="alert alert-success w-100 text-truncate">
         <fa-icon [icon]="['fas', 'circle-check']"></fa-icon>
@@ -36,6 +36,13 @@
         <app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login>
       </div>
     }
+    @else if (error === 'account_limited') {
+      <div class="alert alert-mempool d-block text-center w-100">
+        <div class="d-inline align-middle">
+          <span class="mb-2 mr-2">Your Twitter account does not allow you to access the faucet</span>
+        </div>
+      </div>
+    }
     @else if (error) {
       <!-- User can request -->
       <app-mempool-error class="w-100" [error]="error"></app-mempool-error>
@@ -81,7 +88,7 @@
     }
 
     <!-- Send back coins -->
-    @if (status?.address) {  
+    @if (status?.address) {
       <div class="mt-4 alert alert-info w-100">If you no longer need your testnet4 coins, please consider <a class="text-primary" [routerLink]="['/address/' | relativeUrl, status.address]"><u>sending them back</u></a> to replenish the faucet.</div>
     }
 
diff --git a/frontend/src/app/components/faucet/faucet.component.ts b/frontend/src/app/components/faucet/faucet.component.ts
index 891b6310d..566a3b970 100644
--- a/frontend/src/app/components/faucet/faucet.component.ts
+++ b/frontend/src/app/components/faucet/faucet.component.ts
@@ -19,7 +19,7 @@ export class FaucetComponent implements OnInit, OnDestroy {
   error: string = '';
   user: any = undefined;
   txid: string = '';
- 
+
   faucetStatusSubscription: Subscription;
   status: {
     min: number; // minimum amount to request at once (in sats)
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 9fc2d4e58..1aa13e309 100644
--- a/frontend/src/app/components/master-page/master-page.component.html
+++ b/frontend/src/app/components/master-page/master-page.component.html
@@ -85,7 +85,6 @@
       <li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home" *ngIf="network.val === '' && stateService.env.ACCELERATOR">
         <a class="nav-link" [routerLink]="['/acceleration' | relativeUrl]" (click)="collapse()">
           <fa-icon [icon]="['fas', 'rocket']" [fixedWidth]="true" i18n-title="master-page.accelerator-dashboard" title="Accelerator Dashboard"></fa-icon>
-          <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
         </a>
       </li>
       <li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">
diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
index 2c564882e..50f8b650f 100644
--- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
+++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts
@@ -31,7 +31,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
 
   lastBlockHeight: number;
   blockIndex: number;
-  isLoading$ = new BehaviorSubject<boolean>(true);
+  isLoading$ = new BehaviorSubject<boolean>(false);
   timeLtrSubscription: Subscription;
   timeLtr: boolean;
   chainDirection: string = 'right';
@@ -95,6 +95,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
             }
           }
           this.updateBlock({
+            block: this.blockIndex,
             removed,
             changed,
             added
@@ -110,8 +111,11 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
       if (this.blockGraph) {
         this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection);
       }
-      this.isLoading$.next(true);
-      this.websocketService.startTrackMempoolBlock(changes.index.currentValue);
+      if (!this.websocketService.startTrackMempoolBlock(changes.index.currentValue) && this.stateService.mempoolBlockState && this.stateService.mempoolBlockState.block === changes.index.currentValue) {
+        this.resumeBlock(Object.values(this.stateService.mempoolBlockState.transactions));
+      } else {
+        this.isLoading$.next(true);
+      }
     }
   }
 
@@ -153,6 +157,19 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
     this.isLoading$.next(false);
   }
 
+  resumeBlock(transactionsStripped: TransactionStripped[]): void {
+    if (this.blockGraph) {
+      this.firstLoad = false;
+      this.blockGraph.setup(transactionsStripped, true);
+      this.blockIndex = this.index;
+      this.isLoading$.next(false);
+    } else {
+      requestAnimationFrame(() => {
+        this.resumeBlock(transactionsStripped);
+      });
+    }
+  }
+
   onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
     const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
     if (!event.keyModifier) {
diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.ts b/frontend/src/app/components/mempool-block/mempool-block.component.ts
index 430a456ec..d2e658302 100644
--- a/frontend/src/app/components/mempool-block/mempool-block.component.ts
+++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts
@@ -71,7 +71,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
         })
       );
 
-    this.mempoolBlockTransactions$ = this.stateService.liveMempoolBlockTransactions$.pipe(map(txMap => Object.values(txMap)));
+    this.mempoolBlockTransactions$ = this.stateService.liveMempoolBlockTransactions$.pipe(map(({transactions}) => Object.values(transactions)));
 
     this.network$ = this.stateService.networkChanged$;
   }
diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
index a0958ec40..af5a91c65 100644
--- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
+++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts
@@ -213,7 +213,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
         }
         if (state.mempoolPosition) {
           this.txPosition = state.mempoolPosition;
-          if (this.txPosition.accelerated && !oldTxPosition.accelerated) {
+          if (this.txPosition.accelerated && !oldTxPosition?.accelerated) {
             this.acceleratingArrow = true;
             setTimeout(() => {
               this.acceleratingArrow = false;
diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html b/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html
index 46cda0488..19c08bad9 100644
--- a/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html
+++ b/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html
@@ -19,7 +19,7 @@
       </tr>
       <tr>
         <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
-        <td>{{ rbfInfo.tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td>
+        <td>{{ rbfInfo.tx.fee | number }} <span class="symbol" i18n="shared.sats">sats</span></td>
       </tr>
       <tr *only-vsize>
         <td class="td-width" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
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 48e13de67..34ed23bd0 100644
--- a/frontend/src/app/components/svg-images/svg-images.component.html
+++ b/frontend/src/app/components/svg-images/svg-images.component.html
@@ -93,7 +93,7 @@
     <ng-component *ngTemplateOutlet="researchLogo"></ng-component>
   </ng-container>
   <ng-container *ngSwitchCase="'accelerator'">
-    <svg id="uuid-c6c08704-f501-4e02-a32f-cae17d68f41c" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 2383.736328 551.921875">
+    <svg id="uuid-c6c08704-f501-4e02-a32f-cae17d68f41c" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" [style]="style" viewBox="0 0 2383.736328 551.921875">
       <title>Mempool Accelerator&trade;</title>
       <path d="m2313.990723,368.700684h-33.930176l-24.881836,178.697999h33.929932l12.441162-89.725831c2.764648-19.604004,9.173584-34.055893,19.227051-43.355225,10.053223-9.299332,23.373779-13.948975,39.961914-13.948975,2.764648,0,5.403809.25148,7.916992.75415,2.764648.50267,5.529297,1.131012,8.293945,1.88501l6.786133-36.569092c-4.272949-1.507996-9.173828-2.261963-14.703125-2.261963-11.310059,0-22.368652,3.015839-33.176025,9.047852-10.807373,5.78067-19.101318,13.446411-24.88208,22.99707h-.753906l3.77002-27.520996Zm-270.591675,89.349121c1.759277-13.069336,5.906128-25.259094,12.440796-36.569092s14.703125-21.237625,24.505127-29.782959c9.802002-8.545334,20.735107-15.205627,32.799072-19.980957,12.31543-5.026672,25.133301-7.540039,38.454102-7.540039,13.320557,0,25.384521,2.513367,36.191895,7.540039,10.807373,4.77533,19.855469,11.435623,27.144043,19.980957,7.540039,8.545334,12.943604,18.472961,16.210938,29.782959,3.267334,11.309998,4.021484,23.499756,2.261963,36.569092-2.010498,13.069336-6.283447,25.38472-12.818115,36.946045-6.283203,11.309998-14.325684,21.237625-24.12793,29.782959-9.801758,8.294006-20.860596,14.954285-33.175781,19.980958-12.063965,4.775329-24.756348,7.163086-38.077148,7.163086s-25.510254-2.387757-36.568848-7.163086c-10.807617-5.026673-19.855469-11.686951-27.144043-19.980958-7.288818-8.545334-12.692383-18.472961-16.21106-29.782959-3.267334-11.561325-3.89563-23.876709-1.88501-36.946045Zm36.192017,0c-1.256592,9.048004-1.131104,17.467697.376953,25.259033,1.759277,7.539993,4.775146,14.074677,9.048096,19.604004,4.272461,5.278,9.801758,9.550537,16.587891,12.817871,7.037354,3.016006,15.080078,4.523926,24.12793,4.523926,9.048096,0,17.342041-1.507919,24.88208-4.523926,7.79126-3.267334,14.577393-7.539871,20.358154-12.817871,5.780518-5.529327,10.555664-12.064011,14.325684-19.604004,4.021484-7.791336,6.6604-16.211029,7.916992-25.259033,1.256836-9.047989,1.005371-17.342087-.753906-24.88208-1.507812-7.791336-4.523926-14.326004-9.047852-19.604004-4.272949-5.529327-9.802002-9.802109-16.587891-12.818115-6.786133-3.267334-14.703125-4.900879-23.751221-4.900879s-17.467529,1.633545-25.259033,4.900879c-7.540039,3.016006-14.325928,7.288788-20.35791,12.818115-5.780762,5.278-10.556152,11.812668-14.326172,19.604004-3.77002,7.539993-6.283203,15.834091-7.539795,24.88208Zm-103.342285-59.943115h48.633057l4.146973-29.406006h-48.633057l7.163086-50.140869h-33.930054l-7.162964,50.140869h-39.208008l-4.146973,29.406006h39.207886l-13.194946,93.119141c-1.005371,7.288666-1.759277,14.577301-2.261963,21.865967-.251343,7.037338.879639,13.446381,3.392944,19.227051,2.764648,5.78067,7.414307,10.555923,13.948975,14.325927,6.786011,3.518662,16.713745,5.278077,29.783081,5.278077,1.759277,0,3.89563-.12587,6.408936-.377198,2.764648-.251327,5.529419-.628189,8.294067-1.130859,3.015991-.251328,5.906372-.754059,8.671021-1.508057,3.015991-.753998,5.655029-1.759445,7.916992-3.016113l4.523926-30.913818c-3.77002,2.010666-7.91687,3.392975-12.440918,4.146973-4.272705.753998-8.419678,1.130859-12.44104,1.130859-6.534668,0-11.435669-1.130829-14.703003-3.392822-3.015991-2.513336-5.026611-5.655045-6.031982-9.425049-1.005371-4.021332-1.382324-8.419739-1.130981-13.195068.502686-5.026672,1.130981-10.053406,1.88501-15.080078l11.309937-81.054932Zm-146.485107,63.335938c-8.042725,0-16.33667.502716-24.881958,1.508057-8.545288.753998-16.46228,2.387787-23.750977,4.901123s-13.446411,6.031906-18.473022,10.555908c-5.026611,4.524002-8.042603,10.304703-9.047974,17.342041-1.508057,10.304672.879639,17.718918,7.162964,22.24292,6.283325,4.524002,15.331421,6.786133,27.144043,6.786133,9.299316,0,17.341919-1.508163,24.12793-4.52417,7.037354-3.267334,12.943604-7.414246,17.718994-12.440918s8.4198-10.555893,10.933105-16.587891c2.764648-6.28334,4.523926-12.441147,5.277954-18.473145l1.88501-11.310059h-18.096069Zm-85.955933-70.875977c10.807373-8.796677,22.745605-15.331329,35.814941-19.604004,13.320679-4.524002,26.264404-6.785889,38.831055-6.785889,13.069336,0,24.128052,1.633545,33.176025,4.900879s16.21106,7.665741,21.489014,13.195068c5.277954,5.529327,8.796631,11.938385,10.555908,19.227051,1.759399,7.037323,2.136475,14.451584,1.131104,22.24292l-12.817993,91.234131c-1.005371,6.283325-1.759399,12.064041-2.262085,17.342041-.502686,5.278-.754028,10.304504-.754028,15.079834h-30.159912l.754028-13.572022c.502686-4.524002,1.130859-9.048019,1.884888-13.572021h-.753906c-9.299316,11.56134-19.478271,19.729797-30.536987,24.505127-10.807373,4.775329-22.99707,7.163086-36.569092,7.163086-8.293945,0-16.085327-1.131073-23.374023-3.393067-7.037354-2.261993-13.069336-5.654953-18.095947-10.178955-5.026611-4.524002-8.796631-10.053467-11.309937-16.588135-2.261963-6.785995-2.764771-14.577362-1.508057-23.374023,1.508057-11.56134,5.403687-21.237717,11.687012-29.029053s14.200317-14.074524,23.750977-18.849854c9.802002-5.026672,20.735107-8.545486,32.799072-10.556152,12.315308-2.261993,25.258911-3.392822,38.830933-3.392822h24.881958l1.131104-7.540039c.754028-4.524002.502686-9.048019-.754028-13.572021-1.005371-4.524002-3.015991-8.545288-6.031982-12.063965-3.015991-3.769989-7.037476-6.660461-12.064087-8.671143-5.026611-2.261993-11.184204-3.392822-18.4729-3.392822-6.534668,0-12.441162.628326-17.719116,1.88501-5.026611,1.256668-9.67627,2.890213-13.948975,4.900879-4.021362,1.759338-7.916992,3.895599-11.687012,6.408936-3.518677,2.513336-7.037231,4.901093-10.555908,7.163086l-17.342041-21.112061Zm-95.048218-21.865967h-33.929932l-24.88208,178.697999h33.930054l12.44104-89.725831c2.764648-19.604004,9.173584-34.055893,19.226929-43.355225,10.053345-9.299332,23.374023-13.948975,39.962036-13.948975,2.764648,0,5.403687.25148,7.916992.75415,2.764648.50267,5.529419,1.131012,8.294067,1.88501l6.785889-36.569092c-4.272705-1.507996-9.173706-2.261963-14.703003-2.261963-11.309937,0-22.36853,3.015839-33.175903,9.047852-10.807373,5.78067-19.10144,13.446411-24.88208,22.99707h-.753906l3.769897-27.520996Zm-112.973389,74.645996c.75415-7.539993.502686-14.45153-.753906-20.734863-1.256714-6.283325-3.644409-11.68718-7.163086-16.211182-3.518677-4.77533-8.293945-8.419525-14.325928-10.932861-6.031982-2.764679-13.320679-4.146973-21.865967-4.146973-8.294067,0-16.085449,1.633545-23.374023,4.900879-7.288818,3.015991-13.823486,7.037292-19.604004,12.063965-5.529419,5.026672-10.053467,10.681763-13.572021,16.965088-3.518799,6.031998-5.655029,12.06395-6.409058,18.095947h107.067993Zm-110.837891,27.144043c-1.005371,7.791336-.251465,14.954346,2.261963,21.489014,2.513184,6.283325,6.157593,11.686935,10.932861,16.210938,5.026733,4.524002,10.933105,8.042816,17.719116,10.556152,7.037354,2.513336,14.45166,3.77002,22.243042,3.77002,10.555908,0,19.980957-2.387756,28.274902-7.163086,8.54541-5.026672,16.713623-11.56134,24.505005-19.604004l22.619995,19.604004c-22.11731,24.379333-50.140991,36.569092-84.071045,36.569092-14.074585,0-26.515625-2.387757-37.322998-7.163086-10.555908-4.77533-19.226929-11.309998-26.012939-19.604004-6.786011-8.545334-11.561279-18.472961-14.325928-29.782959-2.764648-11.56134-3.141724-24.002335-1.131104-37.322998,1.759399-13.320663,5.780762-25.636047,12.063965-36.946045,6.283447-11.56134,14.074707-21.488953,23.374023-29.782959,9.550781-8.545319,20.232422-15.205627,32.045044-19.980957,12.064087-4.77533,24.756348-7.163086,38.077026-7.163086,15.833984,0,28.777588,2.764618,38.831055,8.293945,10.304688,5.529327,18.347168,12.817963,24.12793,21.865967,5.78064,8.796661,9.425049,18.850159,10.932983,30.160156,1.507935,11.05867,1.507935,22.368607,0,33.929932l-1.884888,12.063965h-143.26001Zm-172.627686-27.144043c.753906-7.539993.502563-14.45153-.75415-20.734863-1.256592-6.283325-3.644287-11.68718-7.162964-16.211182-3.518677-4.77533-8.294067-8.419525-14.32605-10.932861-6.031982-2.764679-13.320557-4.146973-21.865967-4.146973-8.293945,0-16.085205,1.633545-23.374023,4.900879-7.288574,3.015991-13.823242,7.037292-19.604004,12.063965-5.529297,5.026672-10.053223,10.681763-13.572021,16.965088-3.518555,6.031998-5.654907,12.06395-6.408936,18.095947h107.068115Zm-110.838135,27.144043c-1.005249,7.791336-.251221,14.954346,2.261963,21.489014,2.513428,6.283325,6.157715,11.686935,10.933105,16.210938,5.026611,4.524002,10.932983,8.042816,17.718994,10.556152,7.037354,2.513336,14.45166,3.77002,22.24292,3.77002,10.556152,0,19.981079-2.387756,28.275146-7.163086,8.545288-5.026672,16.713623-11.56134,24.504883-19.604004l22.619995,19.604004c-22.11731,24.379333-50.140991,36.569092-84.070923,36.569092-14.074707,0-26.515747-2.387757-37.322998-7.163086-10.556152-4.77533-19.227051-11.309998-26.013062-19.604004-6.786011-8.545334-11.561401-18.472961-14.32605-29.782959-2.764648-11.56134-3.141602-24.002335-1.130859-37.322998,1.759277-13.320663,5.780518-25.636047,12.063965-36.946045,6.283203-11.56134,14.074707-21.488953,23.374023-29.782959,9.550537-8.545319,20.232178-15.205627,32.044922-19.980957,12.063965-4.77533,24.756348-7.163086,38.077026-7.163086,15.833984,0,28.77771,2.764618,38.830933,8.293945,10.304688,5.529327,18.347412,12.817963,24.128052,21.865967,5.78064,8.796661,9.424927,18.850159,10.932983,30.160156,1.508057,11.05867,1.508057,22.368607,0,33.929932l-1.88501,12.063965h-143.26001Zm-81.591064-68.990967c6.534668,3.267334,12.44104,8.168213,17.718994,14.702881l29.029053-23.374023c-7.540039-9.550659-16.964966-16.587814-28.275024-21.111816-11.058594-4.77533-23.122681-7.163086-36.192017-7.163086-14.074585,0-27.143921,2.387756-39.207947,7.163086-12.064026,4.524002-22.871338,10.933044-32.421997,19.227051-9.299377,8.294006-17.090637,18.221619-23.373962,29.782959-6.032043,11.56134-9.927734,24.253571-11.687012,38.076904-2.010742,13.823334-1.508057,26.515564,1.507935,38.076904,3.015991,11.309998,8.042725,21.112,15.080078,29.406006,7.037292,8.042664,15.959595,14.326096,26.766968,18.850098,10.807373,4.524002,23.122681,6.786133,36.945984,6.786133,13.069397,0,25.636047-2.136506,37.700012-6.40918,12.315308-4.272675,23.625366-11.310059,33.929932-21.112061l-20.734863-24.12793c-6.534668,6.534668-13.446411,11.561417-20.735107,15.080078-7.288574,3.267334-15.708252,4.900879-25.258972,4.900879-8.796692,0-16.587952-1.507919-23.373962-4.523926-6.534668-3.267334-11.938354-7.665741-16.21106-13.195068-4.272644-5.529327-7.288635-11.938141-9.047974-19.226807-1.759338-7.539993-2.01062-15.457214-.754028-23.751221,1.256714-8.293991,3.518677-16.210983,6.786011-23.750977,3.518677-7.791336,8.042725-14.57724,13.572021-20.35791,5.529297-6.031998,11.938354-10.681641,19.22699-13.948975,7.539978-3.518677,15.959656-5.278076,25.258972-5.278076,9.550659,0,17.467773,1.759399,23.750977,5.278076Zm-155.859985,14.702881c-5.277954-6.534668-11.184326-11.435547-17.718994-14.702881-6.283325-3.518677-14.200317-5.278076-23.750977-5.278076-9.299377,0-17.718994,1.759399-25.259033,5.278076-7.288696,3.267334-13.697632,7.916977-19.22699,13.948975-5.529358,5.78067-10.053345,12.566574-13.572021,20.35791-3.267334,7.539993-5.529297,15.456985-6.78595,23.750977-1.256714,8.294006-1.005371,16.211227.753967,23.751221,1.759338,7.288666,4.77533,13.697479,9.048035,19.226807,4.272644,5.529327,9.67627,9.927734,16.210938,13.195068,6.786011,3.016006,14.577393,4.523926,23.374023,4.523926,9.550659,0,17.970337-1.633545,25.259033-4.900879,7.288635-3.518661,14.200317-8.54541,20.734985-15.080078l20.734985,24.12793c-10.304688,9.802002-21.614624,16.839386-33.929993,21.112061-12.064026,4.272674-24.630676,6.40918-37.700012,6.40918-13.823364,0-26.138672-2.262131-36.945984-6.786133-10.807312-4.524003-19.729675-10.807435-26.767029-18.850098-7.037354-8.294006-12.063965-18.096008-15.079956-29.406006-3.016052-11.56134-3.518677-24.253571-1.508057-38.076904,1.759399-13.823334,5.655029-26.515564,11.687012-38.076904,6.283325-11.56134,14.074707-21.488953,23.374023-29.782959,9.550659-8.294006,20.358032-14.703049,32.421997-19.227051,12.063965-4.77533,25.133301-7.163086,39.208008-7.163086,13.069336,0,25.133301,2.387756,36.192017,7.163086,11.309937,4.524002,20.734985,11.561157,28.274963,21.111816l-29.028992,23.374023Zm-224.524109,45.23999c-8.042664,0-16.33667.502716-24.882019,1.508057-8.545288.753998-16.46228,2.387787-23.750977,4.901123s-13.44635,6.031906-18.473022,10.555908-8.042664,10.304703-9.047974,17.342041c-1.507996,10.304672.879639,17.718918,7.162964,22.24292,6.283386,4.524002,15.33136,6.786133,27.144043,6.786133,9.299316,0,17.34198-1.508163,24.127991-4.52417,7.037354-3.267334,12.943665-7.414246,17.718994-12.440918s8.419678-10.555893,10.932983-16.587891c2.764709-6.28334,4.524048-12.441147,5.278015-18.473145l1.88501-11.310059h-18.096008Zm-85.955994-70.875977c10.807312-8.796677,22.745667-15.331329,35.815002-19.604004,13.320679-4.524002,26.264343-6.785889,38.830994-6.785889,13.069336,0,24.127991,1.633545,33.176025,4.900879,9.047974,3.267334,16.210938,7.665741,21.488953,13.195068s8.796692,11.938385,10.55603,19.227051c1.759338,7.037323,2.136353,14.451584,1.130981,22.24292l-12.817993,91.234131c-1.00531,6.283325-1.759338,12.064041-2.262024,17.342041-.502625,5.278-.753967,10.304504-.753967,15.079834h-30.159973l.753967-13.572022c.502686-4.524002,1.130981-9.048019,1.88501-13.572021h-.754028c-9.299316,11.56134-19.478333,19.729797-30.536987,24.505127-10.807312,4.775329-22.997009,7.163086-36.56897,7.163086-8.294006,0-16.085388-1.131073-23.374023-3.393067-7.037354-2.261993-13.069336-5.654953-18.096008-10.178955s-8.796692-10.053467-11.309998-16.588135c-2.262024-6.785995-2.764648-14.577362-1.507996-23.374023,1.507996-11.56134,5.403687-21.237717,11.687012-29.029053s14.200317-14.074524,23.750977-18.849854c9.802002-5.026672,20.734985-8.545486,32.799011-10.556152,12.315308-2.261993,25.258972-3.392822,38.830994-3.392822h24.882019l1.130981-7.540039c.754028-4.524002.502686-9.048019-.753967-13.572021-1.005371-4.524002-3.015991-8.545288-6.032043-12.063965-3.015991-3.769989-7.037292-6.660461-12.063965-8.671143-5.026672-2.261993-11.184326-3.392822-18.473022-3.392822-6.534668,0-12.440979.628326-17.718994,1.88501-5.026672,1.256668-9.676331,2.890213-13.949036,4.900879-4.021301,1.759338-7.916992,3.895599-11.686951,6.408936-3.518677,2.513336-7.037354,4.901093-10.55603,7.163086l-17.34198-21.112061Zm749.776855-87.136963l-34.207275,243.968995h33.929932l34.207275-243.968995h-33.929932Z" style="fill: #A459F6; fill-rule: evenodd; stroke-width: 0px;"/>
       <path d="m870.596436,267.801544v-108.799988c0-16.899994-3.899963-29.899994-11.599976-38.5-7.599976-8.600006-19.100037-12.899994-33.900024-12.899994-16.700012,0-30.400024,5.399994-39.799988,15.5-9.399963,10.200012-14.399963,25.200012-14.399963,43.5v101.299988h-37v-108.899994c0-34.100006-15.400024-51.399994-45.800049-51.399994-16.700012,0-30.399963,5.399994-39.799988,15.5-9.400024,10.200012-14.399963,25.200012-14.399963,43.5v101.299988h-37V74.301575h36v21.099976c14.599976-14.399963,35.599976-22.299988,59.899963-22.299988,29,0,51.899963,11.299988,65,32,15.799988-20.399994,41.300049-32,71.000061-32,24.200012,0,43.399963,7,57.099976,20.700012,14.299988,14.399994,21.799927,36.199982,21.699951,62.899994v111.099976h-37Z" style="fill: #fff; stroke-width: 0px;"/>
@@ -113,6 +113,68 @@
       <polygon points="451.183807 365.144836 431.001465 367.076874 433.694214 395.205902 404.831421 397.968903 406.901459 419.59285 435.764252 416.829849 438.414246 444.512344 458.596588 442.580315 455.946594 414.897827 484.336945 412.180054 482.266907 390.556107 453.876556 393.27388 451.183807 365.144836" style="fill: #A459F6; fill-rule: evenodd; stroke-width: 0px;"/>
     </svg>
   </ng-container>
+  <ng-container *ngSwitchCase="'blocks-2-3'">
+    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" [attr.width]="width" [attr.height]="height" [style]="style" viewBox="0 0 1500 500">
+      <defs> <style> .cls-1 { clip-path: url(#clippath); } .cls-2 { fill: none; } .cls-2, .cls-3, .cls-4, .cls-5, .cls-6, .cls-7, .cls-8, .cls-9 { stroke-width: 0px; } .cls-10 { mask: url(#mask); } .cls-11 { clip-path: url(#clippath-1); } .cls-12 { clip-path: url(#clippath-4); } .cls-13 { clip-path: url(#clippath-3); } .cls-14 { clip-path: url(#clippath-2); } .cls-15 { clip-path: url(#clippath-7); } .cls-16 { clip-path: url(#clippath-8); } .cls-17 { clip-path: url(#clippath-6); } .cls-18 { clip-path: url(#clippath-5); } .cls-3 { fill: #fff; fill-rule: evenodd; } .cls-4 { fill: #2b2725; } .cls-5 { fill: #3d3634; } .cls-6 { fill: #1a1c26; } .cls-7 { fill: #11131f; } .cls-8 { fill: #242837; } .cls-9 { fill: #c4c4c4; } </style> <clipPath id="clippath"> <rect class="cls-2" x="1200.51" y="172.4" width="200.39" height="200.39"/> </clipPath> <clipPath id="clippath-1"> <rect class="cls-2" x="1200.51" y="172.4" width="200.39" height="200.39"/> </clipPath> <clipPath id="clippath-2"> <rect class="cls-2" x="953.3" y="172.4" width="200.39" height="200.39"/> </clipPath> <clipPath id="clippath-3"> <rect class="cls-2" x="953.3" y="172.4" width="200.39" height="200.39"/> </clipPath> <clipPath id="clippath-4"> <rect class="cls-2" x="706.09" y="172.41" width="200.39" height="200.39"/> </clipPath> <clipPath id="clippath-5"> <rect class="cls-2" x="706.09" y="172.41" width="200.39" height="200.39"/> </clipPath> <clipPath id="clippath-6"> <path class="cls-2" d="m123.42,372.8h200.39v-200.39H123.42v200.39Zm247.23,0h200.39v-200.39h-200.39v200.39Z"/> </clipPath> <clipPath id="clippath-7"> <rect class="cls-2" x="123.42" y="172.41" width="447.62" height="200.39"/> </clipPath> <mask id="mask" x="0" y="-207.44" width="1500" height="750" maskUnits="userSpaceOnUse"> <rect class="cls-9" x="620.16" y="74.89" width="33.44" height="364.27"/> </mask> <clipPath id="clippath-8"> <rect class="cls-2" y="-207.44" width="1500" height="750"/> </clipPath> </defs>
+      <g class="cls-1">
+        <g class="cls-11">
+          <image width="836" height="835" transform="translate(1200.4 172.4) scale(.24)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA0IAAANBCAYAAADNyg7aAAAACXBIWXMAAC4jAAAuIwF4pT92AAATRElEQVR4nO3dsQ0ENxJFwZ4DfWUk4xJRMMpUibSMzYEE9KoiaPfhz3K/v///zw4AAEDEn3/9Mf97fQQAAMBtQggAAMgRQgAAQI4QAgAAcoQQAAAQs3N2PBoHAAC0WIQAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAlJ2Zs6+vAAAAuMwiBAAA5Jz5Xp8AAABw1/l9IQcAANDh0zgAACBHCAEAADlejQMAAHIsQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAEDKzs7Z2dd3AAAAXGURAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHLOvr4AAADgMosQAACQc+Z7fQIAAMBdFiEAAKDlE0IAAECQEAIAAHK8GgcAAKTsWIQAAIAgIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAnLOzr28AAAC4yiIEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMg5++3rGwAAAK7ZWYsQAADQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkHP29QUAAACXWYQAAIAcIQQAAOScGR/HAQAALRYhAAAgRwgBAAA5QggAAEjZ8Xw2AAAQZBECAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQM7Zz1+qAgAAId9ahAAAgB4hBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOWdnX98AAABwzY5FCAAACBJCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMg5+/oCAACAyyxCAABAjhACAAByhBAAABCzc2b8SggAAGixCAEAADlCCAAASNnxfDYAABBkEQIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAzpnv9QkAAAB3nZ19fQMAAMBVPo0DAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQM7xd6oAAEDJjkUIAAAIEkIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkeD4bAADIsQgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIOfM9/oEAACAe3Z2zs6+vgMAAOCez6dxAABAkBACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAICcs7OvbwAAALjKIgQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAACk7MycfX0FAADAZRYhAAAgRwgBAAA5Zz4fxwEAACVrEQIAAHqEEAAAkOPVOAAAIMciBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOScnX19AwAAwDU7FiEAACBICAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQM7Zb1/fAAAAcM+3FiEAAKBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAg5+zrCwAAAC7asQgBAABBQggAAMg5v2EIAACgwyIEAADkCCEAACBHCAEAADmezwYAAHIsQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIOTv+UhUAAOjYWYsQAADQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkHP29QUAAACXWYQAAIAcIQQAAOSc+V6fAAAAcJdFCAAAyBFCAABAjhACAABSdjyfDQAABFmEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgJyzs69vAAAAuMoiBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkHP229c3AAAAXLPfWoQAAIAeIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAnLOvLwAAALjMIgQAAOQIIQAAIOfM+DgOAABosQgBAAA5QggAAMjxahwAAJCyYxECAACChBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcs5++/oGAACAi9YiBAAA9AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOScfX0BAADARTsWIQAAIEgIAQAAOec3DAEAAHRYhAAAgBwhBAAA5AghAAAgx/PZAABAjkUIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlnP3+pCgAAdOy3FiEAAKBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcs7Ovr4BAADgKosQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKOv1MFAABKdixCAABAkBACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgx/PZAABAjkUIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlnvtcnAAAA3LOzc3b29R0AAABX+TQOAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJBzdvb1DQAAAFdZhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAICUnZmzr68AAAC4zCIEAADknPlenwAAAHDTzvl9IQcAABDx+TQOAAAIEkIAAECOV+MAAIAcixAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQsjNzdvb1HQAAAFdZhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAICcs68vAAAAuMwiBAAA5AghAAAg58zn4zgAAKBkLUIAAECPEAIAAHK8GgcAAKTsWIQAAIAgIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAnLOzr28AAAC4yiIEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMg5++3rGwAAAK7Zby1CAABAjxACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQM7Z1xcAAABcZhECAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAApO57PBgAAgixCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMg5O/5SFQAAaLEIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByznyvTwAAALhnZ+fs7Os7AAAArvJpHAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByjr9TBQAAaixCAABAjhACAAByhBAAAJBzZvxKCAAAaLEIAQAAOUIIAABI2fF8NgAAEGQRAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAEDOme/1CQAAADftnJ19fQUAAMA9n0/jAACAICEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgJSdmbOvrwAAALjMIgQAAOQIIQAAIOf8vpADAADosAgBAAA5QggAAMjxahwAAJBjEQIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAzpnv9QkAAAD37OycnX19BwAAwFU+jQMAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMg5O/v6BgAAgKssQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAEDO2dcXAAAAXLRjEQIAAIKEEAAAkHPme30CAADARZ9FCAAACBJCAABAjhACAAByPJ8NAADkWIQAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAlJ2ds7Ov7wAAALjKIgQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAnOPvVAEAgBqLEAAAkCOEAACAHCEEAADknBm/EgIAAFosQgAAQI4QAgAAUnY8nw0AAARZhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQc+Z7fQIAAMBNO2dnX18BAABwlU/jAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAApOzNnZ1/fAQAAcJVFCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMg5+/oCAACAyyxCAABAzpnv9QkAAAA37ZwZH8cBAAAhn0/jAACAICEEAADkeDUOAABI2bEIAQAAQUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlnZ1/fAAAAcJVFCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQc/bb1zcAAABcs7MWIQAAoEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkCCEAACDn7OsLAAAALrMIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkOPVOAAAIGXHIgQAAAQJIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAHCEEAADknP329Q0AAAD3fBYhAAAgSAgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIOfs6wsAAAAu2rEIAQAAQUIIAADIOb9hCAAAoGItQgAAQI8QAgAAcoQQAACQ4/lsAAAgxyIEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAApOzMnJ19fQcAAMBVFiEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJAjhAAAgBwhBAAA5AghAAAgRwgBAAA5QggAAMg5870+AQAA4Kads7OvrwAAALjKp3EAAECOEAIAAHKEEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyDn+ThUAACjZsQgBAABBQggAAMgRQgAAQM75fSEHAADQYRECAAByhBAAAJDj+WwAACDHIgQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkCOEAACAnDPf6xMAAADu2dk5O/v6DgAAgHs+n8YBAABBQggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHLOzr6+AQAA4CqLEAAAkCOEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAABAjhACAAByhBAAAJCyM3P29RUAAACXWYQAAIAcIQQAAOSc+V6fAAAAcJdFCAAAyBFCAABAjlfjAACAHIsQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIEUIAAECOEAIAAHKEEAAAkHN29vUNAAAA1+ysRQgAAOgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIEcIAQAAOUIIAADIOfv6AgAAgMssQgAAQI4QAgAAcs58Po4DAABCPosQAAAQJIQAAIAcr8YBAAApOxYhAAAgSAgBAAA5QggAAMgRQgAAQI4QAgAAcoQQAACQI4QAAIAcIQQAAOQIIQAAIOfs7OsbAAAALlqLEAAA0COEAACAHCEEAADkCCEAACBHCAEAADlCCAAAyBFCAAAAAAAA/3X/Au443xUfn7zDAAAAAElFTkSuQmCC"/>
+        </g>
+      </g>
+      <polygon class="cls-8" points="1200.51 172.4 1168.46 128.45 1368.86 128.45 1400.9 172.4 1200.51 172.4"/>
+      <polygon class="cls-6" points="1200.51 172.41 1168.46 128.46 1168.46 328.19 1200.51 372.14 1200.51 172.41"/>
+      <g class="cls-14">
+        <g class="cls-13">
+          <image width="836" height="835" transform="translate(953.2 172.4) scale(.24)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA0EAAANBCAYAAAAm/bXZAAAACXBIWXMAAC4jAAAuIwF4pT92AAATRElEQVR4nO3dsQ0ENxJFwZ4DfWUk4xJRMMpUibSMzYEE9KoiaPfhz3K/v///zw4AAEDAn3/9Mf97fQQAAMBNIggAAEgRQQAAQIoIAgAAUkQQAAAQsnN2PA4HAAB0WIIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAjJ2Zs6+vAAAAuMgSBAAApJz5Xp8AAABwz/l9FQcAANDgczgAACBFBAEAAClehwMAAFIsQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAEDGzs7Z2dd3AAAAXGMJAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFLOvr4AAADgIksQAACQcuZ7fQIAAMA9liAAAKDjE0EAAECMCAIAAFK8DgcAAGTsWIIAAIAYEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAlLOzr28AAAC4xhIEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEg5++3rGwAAAK7YWUsQAADQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkHL29QUAAAAXWYIAAIAUEQQAAKScGR/EAQAAHZYgAAAgRQQBAAApIggAAMjY8UQ2AAAQYwkCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQMrZz9+lAgAAEd9aggAAgBYRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKWdnX98AAABwxY4lCAAAiBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEg5+/oCAACAiyxBAABAiggCAABSRBAAABCyc2b8KggAAOiwBAEAACkiCAAAyNjxRDYAABBjCQIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAypnv9QkAAAD3nJ19fQMAAMA1PocDAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQMrxV6kAAEDFjiUIAACIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkeCIbAABIsQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIOXM9/oEAACAO3Z2zs6+vgMAAOCOz+dwAABAjAgCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAICUs7OvbwAAALjGEgQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAABk7MycfX0FAADARZYgAAAgRQQBAAApZz4fxAEAABVrCQIAAFpEEAAAkOJ1OAAAIMUSBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKScnX19AwAAwBU7liAAACBGBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQMrZb1/fAAAAcMe3liAAAKBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAg5ezrCwAAAC7ZsQQBAAAxIggAAEg5v0EIAACgwRIEAACkiCAAACBFBAEAACmeyAYAAFIsQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIOTv+LhUAAGjYWUsQAADQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkHL29QUAAAAXWYIAAIAUEQQAAKSc+V6fAAAAcI8lCAAASBFBAABAiggCAAAydjyRDQAAxFiCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgJSzs69vAAAAuMYSBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkHL229c3AAAAXLHfWoIAAIAWEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAlLOvLwAAALjIEgQAAKSIIAAAIOXM+CAOAADosAQBAAApIggAAEjxOhwAAJCxYwkCAABiRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUs5++/oGAACAS9YSBAAAtIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKScfX0BAADAJTuWIAAAIEYEAQAAKec3CAEAADRYggAAgBQRBAAApIggAAAgxRPZAABAiiUIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAAClnP3+XCgAANOy3liAAAKBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUs7Ovr4BAADgGksQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFKOv0oFAAAqdixBAABAjAgCAABSRBAAAJAiggAAgBQRBAAApIggAAAgxRPZAABAiiUIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAAClnvtcnAAAA3LGzc3b29R0AAADX+BwOAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJBydvb1DQAAANdYggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAICMnZmzr68AAAC4yBIEAACknPlenwAAAHDLzvl9FQcAABDw+RwOAACIEUEAAECK1+EAAIAUSxAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQsTNzdvb1HQAAANdYggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAICUs68vAAAAuMgSBAAApIggAAAg5czngzgAAKBiLUEAAECLCAIAAFK8DgcAAGTsWIIAAIAYEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAlLOzr28AAAC4xhIEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEg5++3rGwAAAK7Yby1BAABAiwgCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQMrZ1xcAAABcZAkCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAAZO57IBgAAYixBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEg5O/4uFQAA6LAEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSznyvTwAAALhjZ+fs7Os7AAAArvE5HAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSjr9KBQAASixBAABAiggCAABSRBAAAJByZvwqCAAA6LAEAQAAKSIIAADI2PFENgAAEGMJAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAEDKme/1CQAAALfsnJ19fQUAAMAdn8/hAACAGBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgIydmbOvrwAAALjIEgQAAKSIIAAAIOX8vooDAABosAQBAAApIggAAEjxOhwAAJBiCQIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAypnv9QkAAAB37OycnX19BwAAwDU+hwMAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEg5O/v6BgAAgGssQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAEDK2dcXAAAAXLJjCQIAAGJEEAAAkHLme30CAADAJZ8lCAAAiBFBAABAiggCAABSPJENAACkWIIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAjJ2ds7Ov7wAAALjGEgQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAlOOvUgEAgBJLEAAAkCKCAACAFBEEAACknBm/CgIAADosQQAAQIoIAgAAMnY8kQ0AAMRYggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQcuZ7fQIAAMAtO2dnX18BAABwjc/hAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAAZOzNnZ1/fAQAAcI0lCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEg5+/oCAACAiyxBAABAypnv9QkAAAC37JwZH8QBAAARn8/hAACAGBEEAACkeB0OAADI2LEEAQAAMSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAAClnZ1/fAAAAcI0lCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQcvbb1zcAAABcsbOWIAAAoEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkiCAAACDl7OsLAAAALrIEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkOJ1OAAAIGPHEgQAAMSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAFBEEAACknP329Q0AAAB3fJYgAAAgRgQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIOXs6wsAAAAu2bEEAQAAMSIIAABIOb9BCAAAoGAtQQAAQIsIAgAAUkQQAACQ4olsAAAgxRIEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAAZOzMnJ19fQcAAMA1liAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJAiggAAgBQRBAAApIggAAAgRQQBAAApIggAAEg5870+AQAA4Jads7OvrwAAALjG53AAAECKCAIAAFJEEAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASDn+KhUAAKjYsQQBAAAxIggAAEgRQQAAQMr5fRUHAADQYAkCAABSRBAAAJDiiWwAACDFEgQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkCKCAACAlDPf6xMAAADu2Nk5O/v6DgAAgDs+n8MBAAAxIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFLOzr6+AQAA4BpLEAAAkCKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAABAiggCAABSRBAAAJCxM3P29RUAAAAXWYIAAIAUEQQAAKSc+V6fAAAAcI8lCAAASBFBAABAitfhAACAFEsQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIEUEAAECKCAIAAFJEEAAAkHJ29vUNAAAAV+ysJQgAAGgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIEUEAQAAKSIIAABIOfv6AgAAgIssQQAAQIoIAgAAUs58PogDAAAiPksQAAAQI4IAAIAUr8MBAAAZO5YgAAAgRgQBAAApIggAAEgRQQAAQIoIAgAAUkQQAACQIoIAAIAUEQQAAKSIIAAAIOXs7OsbAAAALllLEAAA0CKCAACAFBEEAACkiCAAACBFBAEAACkiCAAASBFBAAAAAAAA/1X/ArWa3xWb1BhHAAAAAElFTkSuQmCC"/>
+        </g>
+      </g>
+      <polygon class="cls-8" points="953.3 172.4 921.25 128.45 1121.65 128.45 1153.69 172.4 953.3 172.4"/>
+      <polygon class="cls-6" points="953.3 172.41 921.25 128.46 921.25 328.19 953.3 372.14 953.3 172.41"/>
+      <g class="cls-12">
+        <g class="cls-18">
+          <image width="836" height="835" transform="translate(706 172.4) scale(.24)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA0QAAANBCAYAAADA1H6dAAAACXBIWXMAAC4jAAAuIwF4pT92AAATOklEQVR4nO3dsbEEQREFwV5idDxCwBGMwVMcaYTvw7VQmRa0WvH25r7//vt/OwAAADH/+s8/5x/XRwAAAFwRRAAAQJYgAgAAsgQRAACQJYgAAIConbfjkTkAAKDJQgQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAAAk7cy8vb4CAADgiIUIAADIevNdnwAAAHDj/X05BwAA0OOTOQAAIEsQAQAAWV6ZAwAAsixEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQNLOztvZ6zsAAABOWIgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACArLfXFwAAAByxEAEAAFlvvusTAAAAbliIAACApk8QAQAAYYIIAADI8socAACQtGMhAgAAwgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALLezl7fAAAAcMJCBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIevvt9Q0AAAA/t7MWIgAAoEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACDr7fUFAAAARyxEAABAliACAACy3oyP5gAAgCYLEQAAkCWIAACALEEEAAAk7Xh2GwAACLMQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACDr7eevWQEAgKBvLUQAAECXIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgKy3s9c3AAAA/NyOhQgAAAgTRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIent9AQAAwBELEQAAkCWIAACALEEEAABE7bwZvyICAACaLEQAAECWIAIAAJJ2PLsNAACEWYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkPXmuz4BAADgxtvZ6xsAAABO+GQOAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAASNqZeXt9BQAAwBELEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFlemQMAALIsRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIevNdnwAAAPB7OztvZ6/vAAAA+L3PJ3MAAECYIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgKy3s9c3AAAAnLAQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAEk7M2+vrwAAADhiIQIAALIEEQAAkPXm89EcAABQtBYiAACgSxABAABZXpkDAACyLEQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABA1tvZ6xsAAAB+bsdCBAAAhAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIevvt9Q0AAAC/962FCAAA6BJEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMh6e30BAADAgR0LEQAAECaIAACArPc3FAEAAPRYiAAAgCxBBAAAZAkiAAAgy7PbAABAloUIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFlvx1+zAgAAPTtrIQIAALoEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACy3l5fAAAAcMRCBAAAZAkiAAAg6813fQIAAMANCxEAAJAliAAAgCxBBAAAJO14dhsAAAizEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZb2evbwAAADhhIQIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMh6++31DQAAAD+331qIAACALkEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgKy31xcAAAAcsRABAABZgggAAMh6Mz6aAwAAmixEAABAliACAACyvDIHAAAk7ViIAACAMEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgKy3317fAAAAcGAtRAAAQJcgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAEDW2+sLAAAADuxYiAAAgDBBBAAAZL2/oQgAAKDHQgQAAGQJIgAAIEsQAQAAWZ7dBgAAsixEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMh6+/lrVgAAoGe/tRABAABdgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkPV29voGAACAExYiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQ9f8sKAAAU7ViIAACAMEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAlme3AQCALAsRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALLefNcnAAAA/N7OztvZ6zsAAABO+GQOAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJD1dvb6BgAAgBMWIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBpZ+bt9RUAAABHLEQAAEDWm+/6BAAAgAs77+/LOQAAgJjPJ3MAAECYIAIAALK8MgcAAGRZiAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAICknZm3s9d3AAAAnLAQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFlvry8AAAA4YiECAACyBBEAAJD15vPRHAAAULQWIgAAoEsQAQAAWV6ZAwAAknYsRAAAQJggAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAEDW29nrGwAAAE5YiAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWW+/vb4BAADg5/ZbCxEAANAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQ9fb6AgAAgCMWIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkLTj2W0AACDMQgQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACArLfjr1kBAIAmCxEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIOvNd30CAADA7+3svJ29vgMAAOCET+YAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkPX8LSsAAFBlIQIAALIEEQAAkCWIAACArDfjV0QAAECThQgAAMgSRAAAQNKOZ7cBAIAwCxEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAst581ycAAABc2Hk7e30FAADA730+mQMAAMIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACSdmbeXl8BAABwxEIEAABkCSIAACDr/X05BwAA0GMhAgAAsgQRAACQ5ZU5AAAgy0IEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgKw33/UJAAAAv7ez83b2+g4AAIATPpkDAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIejt7fQMAAMAJCxEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQ9fb6AgAAgAM7FiIAACBMEAEAAFlvvusTAAAADnwWIgAAIEwQAQAAWYIIAADI8uw2AACQZSECAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAknZ23s5e3wEAAHDCQgQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACArOdvWQEAgCoLEQAAkCWIAACALEEEAABkvRm/IgIAAJosRAAAQJYgAgAAknY8uw0AAIRZiAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQ9ea7PgEAAODCztvZ6ysAAABO+GQOAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJC0M/N29voOAACAExYiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIOvt9QUAAABHLEQAAEDWm+/6BAAAgAs7b8ZHcwAAQNDnkzkAACBMEAEAAFlemQMAAJJ2LEQAAECYIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABA1tvZ6xsAAABOWIgAAIAsQQQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFlvv72+AQAA4Od21kIEAAB0CSIAACBLEAEAAFmCCAAAyBJEAABAliACAACyBBEAAJAliAAAgCxBBAAAZL29vgAAAOCIhQgAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsr8wBAABJOxYiAAAgTBABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIOvtt9c3AAAA/N5nIQIAAMIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACy3l5fAAAAcGDHQgQAAIQJIgAAIOv9DUUAAAA1ayECAAC6BBEAAJAliAAAgCzPbgMAAFkWIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBpZ+bt7PUdAAAAJyxEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQ9ea7PgEAAODCztvZ6ysAAABO+GQOAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFnP37ICAABFOxYiAAAgTBABAABZgggAAMh6f1/OAQAA9FiIAACALEEEAABkeXYbAADIshABAABZgggAAMgSRAAAQJYgAgAAsgQRAACQJYgAAIAsQQQAAGQJIgAAIOvNd30CAADA7+3svJ29vgMAAOD3Pp/MAQAAYYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABAliACAACy3s5e3wAAAHDCQgQAAGQJIgAAIEsQAQAAWYIIAADIEkQAAECWIAIAALIEEQAAkCWIAACALEEEAAAk7cy8vb4CAADgiIUIAADIEkQAAEDWm+/6BAAAgBsWIgAAIEsQAQAAWV6ZAwAAsixEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQNbb2esbAAAAfm5nLUQAAECXIAIAALIEEQAAkCWIAACALEEEAABkCSIAACBLEAEAAFmCCAAAyBJEAABA1tvrCwAAAI5YiAAAgCxBBAAAZL35fDQHAAAEfRYiAAAgTBABAABZXpkDAACSdixEAABAmCACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAgSxABAABZgggAAMgSRAAAQNbb2esbAAAADqyFCAAA6BJEAABAliACAACyBBEAAJAliAAAgCxBBAAAZAkiAAAAAACAmv8DQK/fFefDIbsAAAAASUVORK5CYII="/>
+        </g>
+      </g>
+      <polygon class="cls-8" points="706.09 172.4 674.05 128.45 874.44 128.45 906.49 172.4 706.09 172.4"/>
+      <polygon class="cls-6" points="706.09 172.41 674.05 128.46 674.05 328.19 706.09 372.14 706.09 172.41"/>
+      <g class="cls-17">
+        <g class="cls-15">
+          <image width="1866" height="836" transform="translate(123.28 172.4) scale(.24)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB0cAAANCCAYAAAAdrsSyAAAACXBIWXMAAC4jAAAuIwF4pT92AAAfv0lEQVR4nOzZ0Q0CIRBAQbAfG7QdC9UPKzDRxfhmGrhDWUju7fvt+lgAnzR8quwjp9ief2ThtA6sca91Zp2B33Y9DszlsGP755eYny/Z8/f37ONeEv/lSszJmf3z//fMWmt+/wTmMnN/m5Fxnb11+gWGjK9zfmaPfL+p7B8A3nI5/QIAAAAAAAAAE8RRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAA4NmeHdwwDAIBEHSk1JMmUk36LwBX4SNiZxrAOjA8FgASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLEUQAAAAAAACBBHAUAAAAAAAASxFEAAAAAAAAgQRwFAAAAAAAAEsRRAAAAAAAAIEEcBQAAAAAAABLen+9v9zcAJ1nzS77ml5xfdcNceYi9fE5ktnvuvD8S2edx67qcrkNE/pEtpzUy23Hmeo7lHRm3vN7HcBc+x2wBAAAAAAAAAKi6Afk7L5YgSm3uAAAAAElFTkSuQmCC"/>
+        </g>
+      </g>
+      <polygon class="cls-5" points="370.64 172.41 338.6 128.46 538.99 128.46 571.04 172.41 370.64 172.41"/>
+      <polygon class="cls-5" points="123.43 172.41 91.39 128.46 291.78 128.46 323.82 172.41 123.43 172.41"/>
+      <polygon class="cls-4" points="370.64 172.42 338.6 128.46 338.6 328.2 370.64 372.15 370.64 172.42"/>
+      <polygon class="cls-4" points="123.43 172.42 91.39 128.46 91.39 328.19 123.43 372.15 123.43 172.42"/>
+      <g class="cls-10">
+        <g class="cls-16">
+          <path class="cls-3" d="m635.92,461.08v-9.29h5.29v9.29h-5.29Zm0-27.87v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58s-5.29,0-5.29,0Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-9.29h5.29v9.29h-5.29Z"/>
+        </g>
+      </g>
+    </svg>
+  </ng-container>
+  <ng-container *ngSwitchCase="'blocks-3-2'">
+    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" [attr.width]="width" [attr.height]="height" [style]="style" viewBox="0 0 1500 500">
+      <defs> <style> .cls-1 { clip-path: url(#clippath); } .cls-2 { fill: none; } .cls-2, .cls-3, .cls-4, .cls-5, .cls-6, .cls-7, .cls-8, .cls-9, .cls-10, .cls-11 { stroke-width: 0px; } .cls-12 { mask: url(#mask); } .cls-3 { fill: url(#linear-gradient); } .cls-4 { fill: #fff; fill-rule: evenodd; } .cls-5 { fill: url(#linear-gradient-2); } .cls-6 { fill: #2b2725; } .cls-7 { fill: #3d3634; } .cls-8 { fill: #1a1c26; } .cls-9 { fill: #11131f; } .cls-10 { fill: #242837; } .cls-11 { fill: #c4c4c4; } </style> <mask id="mask" x="-13.45" y="-212.96" width="1500" height="750" maskUnits="userSpaceOnUse"> <rect class="cls-11" x="856.55" y="69.38" width="33.44" height="364.27"/> </mask> <clipPath id="clippath"> <rect class="cls-2" x="-13.45" y="-212.96" width="1500" height="750"/> </clipPath> <linearGradient id="linear-gradient" x1="110.04" y1="266.73" x2="804.8" y2="266.73" gradientUnits="userSpaceOnUse"> <stop offset="0" stop-color="#9c8a00"/> <stop offset="1" stop-color="#cc4f00"/> </linearGradient> <linearGradient id="linear-gradient-2" x1="916.96" y1="272.7" x2="916.96" y2="473.09" gradientTransform="translate(2113.22 640.03) rotate(-180)" gradientUnits="userSpaceOnUse"> <stop offset="0" stop-color="#2c5eab"/> <stop offset="1" stop-color="#8242e1"/> </linearGradient> </defs>
+      <polygon class="cls-10" points="604.4 166.89 572.36 122.93 772.75 122.93 804.79 166.89 604.4 166.89"/>
+      <polygon class="cls-8" points="604.4 166.89 572.36 122.94 572.36 322.67 604.4 366.62 604.4 166.89"/>
+      <polygon class="cls-10" points="357.19 166.89 325.15 122.93 525.54 122.93 557.58 166.89 357.19 166.89"/>
+      <polygon class="cls-8" points="357.19 166.89 325.15 122.94 325.15 322.67 357.19 366.62 357.19 166.89"/>
+      <polygon class="cls-10" points="109.98 166.89 77.94 122.93 278.33 122.93 310.38 166.89 109.98 166.89"/>
+      <polygon class="cls-8" points="109.98 166.89 77.94 122.94 77.94 322.67 109.98 366.62 109.98 166.89"/>
+      <polygon class="cls-7" points="1219.59 166.89 1187.55 122.94 1387.94 122.94 1419.99 166.89 1219.59 166.89"/>
+      <polygon class="cls-7" points="972.38 166.89 940.34 122.94 1140.73 122.94 1172.77 166.89 972.38 166.89"/>
+      <polygon class="cls-6" points="1219.59 166.9 1187.55 122.95 1187.55 322.68 1219.59 366.63 1219.59 166.9"/>
+      <polygon class="cls-6" points="972.38 166.9 940.34 122.95 940.34 322.68 972.38 366.63 972.38 166.9"/>
+      <g class="cls-12">
+        <g class="cls-1">
+          <path class="cls-4" d="m872.31,455.56v-9.29h5.29v9.29h-5.29Zm0-27.87v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-18.58h5.29v18.58h-5.29Zm0-37.16v-9.29h5.29v9.29h-5.29Z"/>
+        </g>
+      </g>
+      <path class="cls-3" d="m804.8,166.86v199.74h-200.39v-199.74h200.39Zm-447.62,0v199.74h200.39v-199.74h-200.39Zm-247.14,0v199.74h200.39v-199.74H110.04Z"/>
+      <path class="cls-5" d="m972.45,367.33v-200.39h200.43v200.39h-200.43Zm447.63-200.39h-200.45v200.39h200.45v-200.39Z"/>
+    </svg>
+  </ng-container>
 </ng-container>
 
 <ng-template #bitcoinLogo let-color let-width="width" let-height="height" let-viewBox="viewBox">
diff --git a/frontend/src/app/components/test-transactions/test-transactions.component.html b/frontend/src/app/components/test-transactions/test-transactions.component.html
index 20ba5c4bd..181db8b01 100644
--- a/frontend/src/app/components/test-transactions/test-transactions.component.html
+++ b/frontend/src/app/components/test-transactions/test-transactions.component.html
@@ -1,5 +1,8 @@
 <div class="container-xl">
-  <h1 class="text-left" i18n="shared.test-transactions|Test Transactions">Test Transactions</h1>
+  <div style="display: flex; width: 100%; align-items: center; flex-wrap: wrap;">
+    <h1 class="text-left" i18n="shared.test-transactions|Test Transactions">Test Transactions</h1>
+    <app-svg-images name="blocks-3-2" style="width: 275px; max-width: 90%; margin-top: -9px"></app-svg-images>
+  </div>
 
   <form [formGroup]="testTxsForm" (submit)="testTxsForm.valid && testTxs()" novalidate>
     <label for="maxfeerate" i18n="test.tx.raw-hex">Raw hex</label>
diff --git a/frontend/src/app/components/time/time.component.ts b/frontend/src/app/components/time/time.component.ts
index 3015007b2..6360bca4a 100644
--- a/frontend/src/app/components/time/time.component.ts
+++ b/frontend/src/app/components/time/time.component.ts
@@ -1,7 +1,6 @@
 import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core';
 import { StateService } from '../../services/state.service';
-import { dates } from '../../shared/i18n/dates';
-import { DatePipe } from '@angular/common';
+import { TimeService } from '../../services/time.service';
 
 @Component({
   selector: 'app-time',
@@ -12,19 +11,9 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
   interval: number;
   text: string;
   tooltip: string;
-  precisionThresholds = {
-    year: 100,
-    month: 18,
-    week: 12,
-    day: 31,
-    hour: 48,
-    minute: 90,
-    second: 90
-  };
-  intervals = {};
 
   @Input() time: number;
-  @Input() dateString: number;
+  @Input() dateString: string;
   @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within' = 'plain';
   @Input() fastRender = false;
   @Input() fixedRender = false;
@@ -40,37 +29,26 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
   constructor(
     private ref: ChangeDetectorRef,
     private stateService: StateService,
-    private datePipe: DatePipe,
-  ) {
-      this.intervals = {
-        year: 31536000,
-        month: 2592000,
-        week: 604800,
-        day: 86400,
-        hour: 3600,
-        minute: 60,
-        second: 1
-      };
-  }
+    private timeService: TimeService,
+  ) {}
 
   ngOnInit() {
+    this.calculateTime();
     if(this.fixedRender){
-      this.text = this.calculate();
       return;
     }
     if (!this.stateService.isBrowser) {
-      this.text = this.calculate();
       this.ref.markForCheck();
       return;
     }
     this.interval = window.setInterval(() => {
-      this.text = this.calculate();
+      this.calculateTime();
       this.ref.markForCheck();
     }, 1000 * (this.fastRender ? 1 : 60));
   }
 
   ngOnChanges() {
-    this.text = this.calculate();
+    this.calculateTime();
     this.ref.markForCheck();
   }
 
@@ -78,224 +56,21 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
     clearInterval(this.interval);
   }
 
-  calculate() {
-    if (this.time == null) {
-      return;
-    }
-
-    let seconds: number;
-    switch (this.kind) {
-      case 'since':
-        seconds = Math.floor((+new Date() - +new Date(this.dateString || this.time * 1000)) / 1000);
-        this.tooltip = this.datePipe.transform(new Date(this.dateString || this.time * 1000), 'yyyy-MM-dd HH:mm');
-        break;
-      case 'until':
-      case 'within':
-        seconds = (+new Date(this.time) - +new Date()) / 1000;
-        this.tooltip = this.datePipe.transform(new Date(this.time), 'yyyy-MM-dd HH:mm');
-        break;
-      default:
-        seconds = Math.floor(this.time);
-        this.tooltip = '';
-    }
-
-    if (!this.showTooltip || this.relative) {
-      this.tooltip = '';
-    }
-
-    if (seconds < 1 && this.kind === 'span') {
-      return $localize`:@@date-base.immediately:Immediately`;
-    } else if (seconds < 60) {
-      if (this.relative || this.kind === 'since') {
-        if (this.lowercaseStart) {
-          return $localize`:@@date-base.just-now:Just now`.charAt(0).toLowerCase() + $localize`:@@date-base.just-now:Just now`.slice(1);
-        }
-        return $localize`:@@date-base.just-now:Just now`;
-      } else if (this.kind === 'until' || this.kind === 'within') {
-        seconds = 60;
-      }
-    }
-
-    let counter: number;
-    const result = [];
-    let usedUnits = 0;
-    for (const [index, unit] of this.units.entries()) {
-      let precisionUnit = this.units[Math.min(this.units.length - 1, index + this.precision)];
-      counter = Math.floor(seconds / this.intervals[unit]);
-      const precisionCounter = Math.round(seconds / this.intervals[precisionUnit]);
-      if (precisionCounter > this.precisionThresholds[precisionUnit]) {
-        precisionUnit = unit;
-      }
-      if (this.units.indexOf(precisionUnit) === this.units.indexOf(this.minUnit)) {
-        counter = Math.max(1, counter);
-      }
-      if (counter > 0) {
-        let rounded;
-        const roundFactor = Math.pow(10,this.fractionDigits || 0);
-        if ((this.kind === 'until' || this.kind === 'within') && usedUnits < this.numUnits) {
-          rounded = Math.floor((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor;
-        } else {
-          rounded = Math.round((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor;
-        }
-        if ((this.kind !== 'until' && this.kind !== 'within')|| this.numUnits === 1) {
-          return this.formatTime(this.kind, precisionUnit, rounded);
-        } else {
-          if (!usedUnits) {
-            result.push(this.formatTime(this.kind, precisionUnit, rounded));
-          } else {
-            result.push(this.formatTime('', precisionUnit, rounded));
-          }
-          seconds -= (rounded * this.intervals[precisionUnit]);
-          usedUnits++;
-          if (usedUnits >= this.numUnits) {
-            return result.join(', ');
-          }
-        }
-      }
-    }
-    return result.join(', ');
-  }
-
-  private formatTime(kind, unit, number): string {
-    const dateStrings = dates(number);
-    switch (kind) {
-      case 'since':
-        if (number === 1) {
-          switch (unit) { // singular (1 day)
-            case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
-            case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
-            case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
-            case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break;
-            case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break;
-            case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; break;
-            case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
-          }
-        } else {
-          switch (unit) { // plural (2 days)
-            case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
-            case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
-            case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
-            case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break;
-            case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break;
-            case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; break;
-            case 'second': return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; break;
-          }
-        }
-        break;
-      case 'until':
-        if (number === 1) {
-          switch (unit) { // singular (In ~1 day)
-            case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
-            case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
-            case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
-            case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break;
-            case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break;
-            case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`;
-            case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
-          }
-        } else {
-          switch (unit) { // plural (In ~2 days)
-            case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
-            case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
-            case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
-            case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break;
-            case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break;
-            case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; break;
-            case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; break;
-          }
-        }
-        break;
-      case 'within':
-        if (number === 1) {
-          switch (unit) { // singular (In ~1 day)
-            case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYear}:DATE:`; break;
-            case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonth}:DATE:`; break;
-            case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeek}:DATE:`; break;
-            case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDay}:DATE:`; break;
-            case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHour}:DATE:`; break;
-            case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinute}:DATE:`;
-            case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSecond}:DATE:`;
-          }
-        } else {
-          switch (unit) { // plural (In ~2 days)
-            case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYears}:DATE:`; break;
-            case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonths}:DATE:`; break;
-            case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeeks}:DATE:`; break;
-            case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDays}:DATE:`; break;
-            case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHours}:DATE:`; break;
-            case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinutes}:DATE:`; break;
-            case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSeconds}:DATE:`; break;
-          }
-        }
-        break;
-      case 'span':
-        if (number === 1) {
-          switch (unit) { // singular (1 day)
-            case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
-            case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
-            case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
-            case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break;
-            case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break;
-            case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; break;
-            case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
-          }
-        } else {
-          switch (unit) { // plural (2 days)
-            case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
-            case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
-            case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
-            case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break;
-            case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break;
-            case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; break;
-            case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; break;
-          }
-        }
-        break;
-      case 'before':
-      if (number === 1) {
-        switch (unit) { // singular (1 day)
-          case 'year': return $localize`:@@time-before:${dateStrings.i18nYear}:DATE: before`; break;
-          case 'month': return $localize`:@@time-before:${dateStrings.i18nMonth}:DATE: before`; break;
-          case 'week': return $localize`:@@time-before:${dateStrings.i18nWeek}:DATE: before`; break;
-          case 'day': return $localize`:@@time-before:${dateStrings.i18nDay}:DATE: before`; break;
-          case 'hour': return $localize`:@@time-before:${dateStrings.i18nHour}:DATE: before`; break;
-          case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinute}:DATE: before`; break;
-          case 'second': return $localize`:@@time-before:${dateStrings.i18nSecond}:DATE: before`; break;
-        }
-      } else {
-        switch (unit) { // plural (2 days)
-          case 'year': return $localize`:@@time-before:${dateStrings.i18nYears}:DATE: before`; break;
-          case 'month': return $localize`:@@time-before:${dateStrings.i18nMonths}:DATE: before`; break;
-          case 'week': return $localize`:@@time-before:${dateStrings.i18nWeeks}:DATE: before`; break;
-          case 'day': return $localize`:@@time-before:${dateStrings.i18nDays}:DATE: before`; break;
-          case 'hour': return $localize`:@@time-before:${dateStrings.i18nHours}:DATE: before`; break;
-          case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinutes}:DATE: before`; break;
-          case 'second': return $localize`:@@time-before:${dateStrings.i18nSeconds}:DATE: before`; break;
-        }
-      }
-      break;
-      default:
-        if (number === 1) {
-          switch (unit) { // singular (1 day)
-            case 'year': return dateStrings.i18nYear; break;
-            case 'month': return dateStrings.i18nMonth; break;
-            case 'week': return dateStrings.i18nWeek; break;
-            case 'day': return dateStrings.i18nDay; break;
-            case 'hour': return dateStrings.i18nHour; break;
-            case 'minute': return dateStrings.i18nMinute; break;
-            case 'second': return dateStrings.i18nSecond; break;
-          }
-        } else {
-          switch (unit) { // plural (2 days)
-            case 'year': return dateStrings.i18nYears; break;
-            case 'month': return dateStrings.i18nMonths; break;
-            case 'week': return dateStrings.i18nWeeks; break;
-            case 'day': return dateStrings.i18nDays; break;
-            case 'hour': return dateStrings.i18nHours; break;
-            case 'minute': return dateStrings.i18nMinutes; break;
-            case 'second': return dateStrings.i18nSeconds; break;
-          }
-        }
-    }
+  calculateTime(): void {
+    const { text, tooltip } = this.timeService.calculate(
+      this.time,
+      this.kind,
+      this.relative,
+      this.precision,
+      this.minUnit,
+      this.showTooltip,
+      this.units,
+      this.dateString,
+      this.lowercaseStart,
+      this.numUnits,
+      this.fractionDigits,
+    );
+    this.text = text;
+    this.tooltip = tooltip;
   }
 }
diff --git a/frontend/src/app/components/tracker/tracker.component.html b/frontend/src/app/components/tracker/tracker.component.html
index d467aae80..4e222479b 100644
--- a/frontend/src/app/components/tracker/tracker.component.html
+++ b/frontend/src/app/components/tracker/tracker.component.html
@@ -42,7 +42,7 @@
       <div class="blockchain-wrapper" [style]="{ height: blockchainHeight * 1.16 + 'px' }">
         <app-clockchain [height]="blockchainHeight" [width]="blockchainWidth" mode="none"></app-clockchain>
       </div>
-    <div class="panel">
+    <div class="panel" *ngIf="!error || waitingForTransaction">
       @if (replaced) {
         <div class="alert-replaced" role="alert">
           <span i18n="transaction.rbf.replacement|RBF replacement">This transaction has been replaced by:</span>
@@ -65,23 +65,25 @@
               }
             </div>
           </div>
-          <div class="field narrower">
-            <div class="label" i18n="transaction.eta|Transaction ETA">ETA</div>
-            <div class="value">
-              <ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton">
-                <span class="justify-content-end d-flex align-items-center">
-                  @if (eta.blocks >= 7) {
-                    <span i18n="transaction.eta.not-any-time-soon|Transaction ETA mot any time soon">Not any time soon</span>
-                  } @else {
-                    <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
-                  }
-                </span>
-              </ng-container>
-              <ng-template #etaSkeleton>
-                <span class="skeleton-loader" style="max-width: 200px;"></span>
-              </ng-template>
-            </div>
-          </div>
+          @if (!replaced) {
+            <div class="field narrower">
+              <div class="label" i18n="transaction.eta|Transaction ETA">ETA</div>
+              <div class="value">
+                <ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton">
+                  <span class="justify-content-end d-flex align-items-center">
+                    @if (eta.blocks >= 7) {
+                      <span i18n="transaction.eta.not-any-time-soon|Transaction ETA mot any time soon">Not any time soon</span>
+                    } @else {
+                      <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
+                    }
+                  </span>
+                </ng-container>
+                <ng-template #etaSkeleton>
+                  <span class="skeleton-loader" style="max-width: 200px;"></span>
+                </ng-template>
+              </div>
+            </div>  
+          }
         } @else if (tx && tx.status?.confirmed) {
           <div class="field narrower mt-2">
             <div class="label" i18n="transaction.confirmed-at">Confirmed at</div>
@@ -111,7 +113,7 @@
       </div>
     </div>
 
-    <div class="bottom-panel">
+    <div class="bottom-panel" *ngIf="!error || waitingForTransaction">
       @if (isLoading) {
         <div class="progress-icon">
           <div class="spinner-border text-light" style="width: 1em; height: 1em"></div>
@@ -184,6 +186,12 @@
         </div>
       }
     </div>
+    
+    <div class="bottom-panel" *ngIf="error && !waitingForTransaction">
+      <app-http-error [error]="error">
+        <span i18n="transaction.error.loading-transaction-data">Error loading transaction data.</span>
+      </app-http-error>
+    </div>
 
     <div class="footer-link"
       [routerLink]="['/tx' | relativeUrl, tx?.txid || txId]"
diff --git a/frontend/src/app/components/tracker/tracker.component.ts b/frontend/src/app/components/tracker/tracker.component.ts
index c869f9705..9c0c6f9bd 100644
--- a/frontend/src/app/components/tracker/tracker.component.ts
+++ b/frontend/src/app/components/tracker/tracker.component.ts
@@ -286,14 +286,14 @@ export class TrackerComponent implements OnInit, OnDestroy {
         this.accelerationInfo = null;
       }),
       switchMap((blockHash: string) => {
-        return this.servicesApiService.getAccelerationHistory$({ blockHash });
+        return this.servicesApiService.getAllAccelerationHistory$({ blockHash }, null, this.txId);
       }),
       catchError(() => {
         return of(null);
       })
     ).subscribe((accelerationHistory) => {
       for (const acceleration of accelerationHistory) {
-        if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional')) {
+        if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional') && acceleration.pools.includes(acceleration.minedByPoolUniqueId)) {
           const boostCost = acceleration.boostCost || acceleration.bidBoost;
           acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize;
           acceleration.boost = boostCost;
@@ -747,7 +747,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
 
   checkAccelerationEligibility() {
     if (this.tx) {
-      this.tx.flags = getTransactionFlags(this.tx);
+      this.tx.flags = getTransactionFlags(this.tx, null, null, this.tx.status?.block_time, this.stateService.network);
       const replaceableInputs = (this.tx.flags & (TransactionFlags.sighash_none | TransactionFlags.sighash_acp)) > 0n;
       const highSigop = (this.tx.sigops * 20) > this.tx.weight;
       this.eligibleForAcceleration = !replaceableInputs && !highSigop;
diff --git a/frontend/src/app/components/trademark-policy/trademark-policy.component.html b/frontend/src/app/components/trademark-policy/trademark-policy.component.html
index de1d78daa..e12cbb8b2 100644
--- a/frontend/src/app/components/trademark-policy/trademark-policy.component.html
+++ b/frontend/src/app/components/trademark-policy/trademark-policy.component.html
@@ -8,7 +8,7 @@
   <div *ngIf="officialMempoolSpace">
     <h2>Trademark Policy and Guidelines</h2>
     <h5>The Mempool Open Source Project &reg;</h5>
-    <h6>Updated: July 3, 2024</h6>
+    <h6>Updated: August 19, 2024</h6>
     <br>
 
     <div class="text-left">
@@ -95,16 +95,31 @@
             <p>The mempool Square Logo</p>
             <br><br>
 
-            <app-svg-images name="accelerator" height="76px"></app-svg-images>
+            <app-svg-images name="accelerator" style="width: 500px; max-width: 80%"></app-svg-images>
             <br><br>
             <p>The Mempool Accelerator Logo</p>
             <br><br>
 
+            <img src="/resources/mempool-research.png" style="width: 500px; max-width: 80%">
+            <br><br>
+            <p>The mempool research Logo</p>
+            <br><br>
+
             <app-svg-images name="goggles" height="96px"></app-svg-images>
             <br><br>
             <p>The Mempool Goggles Logo</p>
             <br><br>
 
+            <img src="/resources/mempool-transaction.png" style="width: 500px; max-width: 80%">
+            <br><br>
+            <p>The mempool transaction Logo</p>
+            <br><br>
+
+            <img src="/resources/mempool-block-visualization.png" style="width: 500px; max-width: 80%">
+            <br><br>
+            <p>The mempool block visualization Logo</p>
+            <br><br>
+
             <img src="/resources/mempool-blocks-2-3-logo.jpeg" style="width: 500px; max-width: 80%">
             <br><br>
             <p>The mempool Blocks Logo</p>
diff --git a/frontend/src/app/components/transaction/transaction-preview.component.html b/frontend/src/app/components/transaction/transaction-preview.component.html
index 63a11a8f0..066e0d442 100644
--- a/frontend/src/app/components/transaction/transaction-preview.component.html
+++ b/frontend/src/app/components/transaction/transaction-preview.component.html
@@ -21,7 +21,7 @@
       </ng-template>
     </span>
     <span class="field col-sm-4 text-center"><ng-container *ngIf="transactionTime > 0">&lrm;{{ transactionTime * 1000 | date:'yyyy-MM-dd HH:mm' }}</ng-container></span>
-    <span class="field col-sm-4 text-right"><span class="label" i18n="transaction.fee|Transaction fee">Fee</span> {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></span>
+    <span class="field col-sm-4 text-right"><span class="label" i18n="transaction.fee|Transaction fee">Fee</span> {{ tx.fee | number }} <span class="symbol" i18n="shared.sats">sats</span></span>
   </div>
 
 
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index ecd00e599..ec06dd5ad 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -551,23 +551,23 @@
         <td class="td-width align-items-center align-middle" i18n="transaction.eta|Transaction ETA">ETA</td>
         <td>
           <ng-container *ngIf="(ETA$ | async) as eta; else etaSkeleton">
-            @if (eta.blocks >= 7) {
-              <span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) ? 'etaDeepMempool justify-content-end align-items-center' : ''">
-                <span i18n="transaction.eta.not-any-time-soon|Transaction ETA mot any time soon">Not any time soon</span>
-                @if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) {
-                  <a class="btn btn-sm accelerateDeepMempool btn-small-height float-right" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
-                }
-              </span>
-            } @else if (network === 'liquid' || network === 'liquidtestnet') {
+            @if (network === 'liquid' || network === 'liquidtestnet') {
               <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
             } @else {
-              <span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) ? 'etaDeepMempool justify-content-end align-items-center' : ''">
-                <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
-                @if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && eligibleForAcceleration) {
-                  <a class="btn btn-sm accelerateDeepMempool btn-small-height float-right" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
+              <span [class]="(!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) ? 'etaDeepMempool d-flex justify-content-between' : ''">
+                @if (eta.blocks >= 7) {
+                  <span i18n="transaction.eta.not-any-time-soon|Transaction ETA mot any time soon">Not any time soon</span>
+                } @else {
+                  <app-time kind="until" [time]="eta.time" [fastRender]="false" [fixedRender]="true"></app-time>
+                }
+                @if (!tx?.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !showAccelerationSummary && notAcceleratedOnLoad) {
+                  <div class="d-flex accelerate">
+                    <a class="btn btn-sm accelerateDeepMempool btn-small-height" [class.disabled]="!eligibleForAcceleration" i18n="transaction.accelerate|Accelerate button label" (click)="onAccelerateClicked()">Accelerate</a>
+                    <a *ngIf="!eligibleForAcceleration" href="https://mempool.space/accelerator#why-cant-accelerate" target="_blank" class="info-badges ml-1" i18n-ngbTooltip="Mempool Accelerator&trade; tooltip" ngbTooltip="This transaction cannot be accelerated">
+                        <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
+                    </a>
+                  </div>
                 }
-              </span>
-              <span class="eta justify-content-end">
               </span>
             }
           </ng-container>
@@ -606,16 +606,11 @@
   @if (!isLoadingTx) {
     <tr>
       <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
-      <td class="text-wrap">{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> 
-        @if (accelerationInfo?.bidBoost) {
-          <span class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band"> +{{ accelerationInfo.bidBoost | number }} </span><span class="symbol" i18n="shared.sat|sat">sat</span>
-          <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee + accelerationInfo.bidBoost"></app-fiat></span>
-        } @else if (tx.feeDelta && !accelerationInfo) {
-          <span class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band"> +{{ tx.feeDelta | number }} </span><span class="symbol" i18n="shared.sat|sat">sat</span>
-          <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee + tx.feeDelta"></app-fiat></span>
-        } @else {
-          <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee"></app-fiat></span>
-        }        
+      <td class="text-wrap">{{ tx.fee | number }} <span class="symbol" i18n="shared.sats">sats</span>
+        @if (accelerationInfo?.bidBoost ?? tx.feeDelta > 0) {
+          <span class="oobFees" i18n-ngbTooltip="Acceleration Fees" ngbTooltip="Acceleration fees paid out-of-band"> +{{ accelerationInfo?.bidBoost ?? tx.feeDelta | number }} </span><span class="symbol" i18n="shared.sats">sats</span>
+        }
+        <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee + ((accelerationInfo?.bidBoost ?? tx.feeDelta) || 0)"></app-fiat></span>
       </td>
     </tr>
   } @else {
@@ -675,7 +670,7 @@
 <ng-template #acceleratingRow>
   <tr>
     <td rowspan="2" colspan="2" style="padding: 0;">
-      <app-active-acceleration-box [tx]="tx" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats" [hasCpfp]="hasCpfp" (toggleCpfp)="showCpfpDetails = !showCpfpDetails" [chartPositionLeft]="isMobile"></app-active-acceleration-box>
+      <app-active-acceleration-box [acceleratedBy]="tx.acceleratedBy" [effectiveFeeRate]="tx.effectiveFeePerVsize" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats" [hasCpfp]="hasCpfp" (toggleCpfp)="showCpfpDetails = !showCpfpDetails" [chartPositionLeft]="isMobile"></app-active-acceleration-box>
     </td>
   </tr>
   <tr></tr>
@@ -689,8 +684,15 @@
         @if (pool) {
           <td class="wrap-cell">
             <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: #FFF;padding:0;">
+              <span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
+                @if (pool.minerNames[1].length > 16) {
+                  {{ pool.minerNames[1].slice(0, 15) }}…
+                } @else {
+                  {{ pool.minerNames[1] }}
+                }
+              </span>
               <img class="pool-logo" [src]="'/resources/mining-pools/' + pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pool.name + ' mining pool'">
-              {{ pool.name  }}
+              {{ pool.name }}
             </a>
           </td>
         } @else {
diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss
index 232a2cacb..42325a1b4 100644
--- a/frontend/src/app/components/transaction/transaction.component.scss
+++ b/frontend/src/app/components/transaction/transaction.component.scss
@@ -60,6 +60,19 @@
 	top: -1px;
 }
 
+.miner-name {
+  margin-right: 4px;
+  vertical-align: top;
+}
+
+.pool-logo {
+  width: 25px;
+  height: 25px;
+  position: relative;
+  top: -1px;
+  margin-right: 2px;
+}
+
 .badge.badge-accelerated {
   background-color: var(--tertiary);
   color: white;
@@ -287,37 +300,21 @@
 }
 
 .accelerate {
-  display: flex !important;
-  align-self: auto;
-  margin-left: auto;
-  background-color: var(--tertiary);
-  @media (max-width: 849px) {
-    margin-left: 5px;
-  }
+  @media (min-width: 850px) {
+    margin-left: auto;
+  }  
 }
 
 .etaDeepMempool {
-  justify-content: flex-end;
   flex-wrap: wrap;
-  align-content: center;
-  @media (max-width: 995px) {
-    justify-content: left !important;
-  }
   @media (max-width: 849px) {
     justify-content: right !important;
   }
 }
 
 .accelerateDeepMempool {
-  align-self: auto;
-  margin-left: auto;
   background-color: var(--tertiary);
-  @media (max-width: 995px) {
-    margin-left: 0px;
-  }
-  @media (max-width: 849px) {
-    margin-left: 5px;
-  }
+  margin-left: 5px;
 }
 
 .goggles-icon {
@@ -335,4 +332,9 @@
 
 .oobFees {
   color: #905cf4;
+}
+
+.disabled {
+  opacity: 0.5;
+  pointer-events: none;
 }
\ No newline at end of file
diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts
index bcad164cc..1306c432d 100644
--- a/frontend/src/app/components/transaction/transaction.component.ts
+++ b/frontend/src/app/components/transaction/transaction.component.ts
@@ -42,6 +42,7 @@ interface Pool {
   id: number;
   name: string;
   slug: string;
+  minerNames: string[] | null;
 }
 
 export interface TxAuditStatus {
@@ -139,6 +140,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
   firstLoad = true;
   waitingForAccelerationInfo: boolean = false;
   isLoadingFirstSeen = false;
+  notAcceleratedOnLoad: boolean = null;
 
   featuresEnabled: boolean;
   segwitEnabled: boolean;
@@ -191,7 +193,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
     this.hideAccelerationSummary = this.stateService.isMempoolSpaceBuild ? this.storageService.getValue('hide-accelerator-pref') == 'true' : true;
 
     if (!this.stateService.isLiquid()) {
-      this.miningService.getMiningStats('1w').subscribe(stats => {
+      this.miningService.getMiningStats('1m').subscribe(stats => {
         this.miningStats = stats;
       });
     }
@@ -343,7 +345,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
         this.setIsAccelerated();
       }),
       switchMap((blockHeight: number) => {
-        return this.servicesApiService.getAccelerationHistory$({ blockHeight }).pipe(
+        return this.servicesApiService.getAllAccelerationHistory$({ blockHeight }, null, this.txId).pipe(
           switchMap((accelerationHistory: Acceleration[]) => {
             if (this.tx.acceleration && !accelerationHistory.length) { // If the just mined transaction was accelerated, but services backend did not return any acceleration data, retry
               return throwError('retry');
@@ -358,12 +360,18 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
       }),
     ).subscribe((accelerationHistory) => {
       for (const acceleration of accelerationHistory) {
-        if (acceleration.txid === this.txId && (acceleration.status === 'completed' || acceleration.status === 'completed_provisional')) {
-          const boostCost = acceleration.boostCost || acceleration.bidBoost;
-          acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize;
-          acceleration.boost = boostCost;
-          this.tx.acceleratedAt = acceleration.added;
-          this.accelerationInfo = acceleration;
+        if (acceleration.txid === this.txId) {
+          if (acceleration.status === 'completed' || acceleration.status === 'completed_provisional') {
+            if (acceleration.pools.includes(acceleration.minedByPoolUniqueId)) {
+              const boostCost = acceleration.boostCost || acceleration.bidBoost;
+              acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize;
+              acceleration.boost = boostCost;
+              this.tx.acceleratedAt = acceleration.added;
+              this.accelerationInfo = acceleration;  
+            } else {
+              this.tx.feeDelta = undefined;
+            }
+          }
           this.waitingForAccelerationInfo = false;
           this.setIsAccelerated();
         }
@@ -484,7 +492,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
           if (this.stateService.network === '') {
             if (!this.mempoolPosition.accelerated) {
               if (!this.accelerationFlowCompleted && !this.hideAccelerationSummary && !this.showAccelerationSummary) {
-                this.miningService.getMiningStats('1w').subscribe(stats => {
+                this.miningService.getMiningStats('1m').subscribe(stats => {
                   this.miningStats = stats;
                 });
               }
@@ -842,6 +850,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
       this.tx.feeDelta = cpfpInfo.feeDelta;
       this.setIsAccelerated(firstCpfp);
     }
+    
+    if (this.notAcceleratedOnLoad === null) {
+      this.notAcceleratedOnLoad = !this.isAcceleration;
+    }
 
     if (!this.isAcceleration && this.fragmentParams.has('accelerate')) {
       this.forceAccelerationSummary = true;
@@ -895,7 +907,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
       this.segwitEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'segwit');
       this.taprootEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'taproot');
       this.rbfEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'rbf');
-      this.tx.flags = getTransactionFlags(this.tx);
+      this.tx.flags = getTransactionFlags(this.tx, null, null, this.tx.status?.block_time, this.stateService.network);
       this.filters = this.tx.flags ? toFilters(this.tx.flags).filter(f => f.txPage) : [];
       this.checkAccelerationEligibility();
     } else {
@@ -960,6 +972,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
     this.filters = [];
     this.showCpfpDetails = false;
     this.showAccelerationDetails = false;
+    this.accelerationFlowCompleted = false;
     this.accelerationInfo = null;
     this.cashappEligible = false;
     this.txInBlockIndex = null;
@@ -1077,6 +1090,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
         (!this.hideAccelerationSummary && !this.accelerationFlowCompleted)
         || this.forceAccelerationSummary
       )
+      && this.notAcceleratedOnLoad // avoid briefly showing accelerator checkout on already accelerated txs
     );
   }
 
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html
index 8954e4ecb..9b88678b4 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.html
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.html
@@ -321,7 +321,7 @@
       <div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase && tx.fee !== -1">
         <app-fee-rate [fee]="tx.fee" [weight]="tx.weight"></app-fee-rate>
         <span class="d-none d-sm-inline-block">&nbsp;&ndash; {{ tx.fee | number }} <span class="symbol"
-            i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee"></app-fiat></span></span>
+            i18n="shared.sats">sats</span> <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee"></app-fiat></span></span>
       </div>
       <div class="float-left mt-2-5 grey-info-text" *ngIf="tx.fee === -1" i18n="transactions-list.load-to-reveal-fee-info">Show more inputs to reveal fee data</div>
 
diff --git a/frontend/src/app/components/utxo-graph/utxo-graph.component.html b/frontend/src/app/components/utxo-graph/utxo-graph.component.html
new file mode 100644
index 000000000..462e4328e
--- /dev/null
+++ b/frontend/src/app/components/utxo-graph/utxo-graph.component.html
@@ -0,0 +1,21 @@
+<app-indexing-progress *ngIf="!widget"></app-indexing-progress>
+
+<div [class.full-container]="!widget">
+  <ng-container *ngIf="!error">
+    <div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null, paddingBottom: !widget}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
+      (chartInit)="onChartInit($event)">
+    </div>
+    <div class="text-center loadingGraphs" *ngIf="isLoading">
+      <div class="spinner-border text-light"></div>
+    </div>
+  </ng-container>
+  <ng-container *ngIf="error">
+    <div class="error-wrapper">
+      <p class="error">{{ error }}</p>
+    </div>
+  </ng-container>
+
+  <div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
+    <div class="spinner-border text-light"></div>
+  </div>
+</div>
diff --git a/frontend/src/app/components/utxo-graph/utxo-graph.component.scss b/frontend/src/app/components/utxo-graph/utxo-graph.component.scss
new file mode 100644
index 000000000..1b5e0320d
--- /dev/null
+++ b/frontend/src/app/components/utxo-graph/utxo-graph.component.scss
@@ -0,0 +1,59 @@
+.card-header {
+  border-bottom: 0;
+  font-size: 18px;
+  @media (min-width: 465px) {
+    font-size: 20px;
+  }
+  @media (min-width: 992px) {
+    height: 40px;
+  }
+}
+
+.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;
+  width: 100%;
+  height: 400px;
+}
+
+.error-wrapper {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  align-items: center;
+  justify-content: center;
+
+  font-size: 15px;
+  color: grey;
+  font-weight: bold;
+}
+
+.chart {
+  display: flex;
+  flex: 1;
+  width: 100%;
+  padding-right: 10px;
+}
+.chart-widget {
+  width: 100%;
+  height: 100%;
+}
+
+.disabled {
+  pointer-events: none;
+  opacity: 0.5;
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/utxo-graph/utxo-graph.component.ts b/frontend/src/app/components/utxo-graph/utxo-graph.component.ts
new file mode 100644
index 000000000..3a549c1e7
--- /dev/null
+++ b/frontend/src/app/components/utxo-graph/utxo-graph.component.ts
@@ -0,0 +1,374 @@
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, NgZone, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
+import { EChartsOption } from '../../graphs/echarts';
+import { Subscription } from 'rxjs';
+import { Utxo } from '../../interfaces/electrs.interface';
+import { StateService } from '../../services/state.service';
+import { Router } from '@angular/router';
+import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
+import { renderSats } from '../../shared/common.utils';
+import { colorToHex, hexToColor, mix } from '../block-overview-graph/utils';
+import { TimeService } from '../../services/time.service';
+
+const newColorHex = '1bd8f4';
+const oldColorHex = '9339f4';
+const pendingColorHex = 'eba814';
+const newColor = hexToColor(newColorHex);
+const oldColor = hexToColor(oldColorHex);
+
+interface Circle {
+  x: number,
+  y: number,
+  r: number,
+  i: number,
+}
+
+interface UtxoCircle extends Circle {
+  utxo: Utxo;
+}
+
+function sortedInsert(positions: { c1: Circle, c2: Circle, d: number, p: number, side?: boolean }[], newPosition: { c1: Circle, c2: Circle, d: number, p: number }): void {
+  let left = 0;
+  let right = positions.length;
+  while (left < right) {
+    const mid = Math.floor((left + right) / 2);
+    if (positions[mid].p > newPosition.p) {
+      right = mid;
+    } else {
+      left = mid + 1;
+    }
+  }
+  positions.splice(left, 0, newPosition, {...newPosition, side: true });
+}
+@Component({
+  selector: 'app-utxo-graph',
+  templateUrl: './utxo-graph.component.html',
+  styleUrls: ['./utxo-graph.component.scss'],
+  styles: [`
+    .loadingGraphs {
+      position: absolute;
+      top: 50%;
+      left: calc(50% - 15px);
+      z-index: 99;
+    }
+  `],
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class UtxoGraphComponent implements OnChanges, OnDestroy {
+  @Input() utxos: Utxo[];
+  @Input() height: number = 200;
+  @Input() right: number | string = 10;
+  @Input() left: number | string = 70;
+  @Input() widget: boolean = false;
+
+  subscription: Subscription;
+  lastUpdate: number = 0;
+  updateInterval;
+
+  chartOptions: EChartsOption = {};
+  chartInitOptions = {
+    renderer: 'svg',
+  };
+
+  error: any;
+  isLoading = true;
+  chartInstance: any = undefined;
+
+  constructor(
+    public stateService: StateService,
+    private cd: ChangeDetectorRef,
+    private zone: NgZone,
+    private router: Router,
+    private relativeUrlPipe: RelativeUrlPipe,
+    private timeService: TimeService,
+  ) {
+    // re-render the chart every 10 seconds, to keep the age colors up to date
+    this.updateInterval = setInterval(() => {
+      if (this.lastUpdate < Date.now() - 10000 && this.utxos) {
+        this.prepareChartOptions(this.utxos);
+      }
+    }, 10000);
+  }
+
+  ngOnChanges(changes: SimpleChanges): void {
+    this.isLoading = true;
+    if (!this.utxos) {
+      return;
+    }
+    if (changes.utxos) {
+      this.prepareChartOptions(this.utxos);
+    }
+  }
+
+  prepareChartOptions(utxos: Utxo[]): void {
+    if (!utxos || utxos.length === 0) {
+      return;
+    }
+
+    this.isLoading = false;
+
+    // Helper functions
+    const distance = (x1: number, y1: number, x2: number, y2: number): number => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
+    const intersection = (c1: Circle, c2: Circle, d: number, r: number, side: boolean): { x: number, y: number} => {
+      const d1 = c1.r + r;
+      const d2 = c2.r + r;
+      const a = (d1 * d1 - d2 * d2 + d * d) / (2 * d);
+      const h = Math.sqrt(d1 * d1 - a * a);
+      const x3 = c1.x + a * (c2.x - c1.x) / d;
+      const y3 = c1.y + a * (c2.y - c1.y) / d;
+      return side
+        ? { x: x3 + h * (c2.y - c1.y) / d, y: y3 - h * (c2.x - c1.x) / d }
+        : { x: x3 - h * (c2.y - c1.y) / d, y: y3 + h * (c2.x - c1.x) / d };
+    };
+
+    // ~Linear algorithm to pack circles as tightly as possible without overlaps
+    const placedCircles: UtxoCircle[] = [];
+    const positions: { c1: Circle, c2: Circle, d: number, p: number, side?: boolean }[] = [];
+    // Pack in descending order of value, and limit to the top 500 to preserve performance
+    const sortedUtxos = utxos.sort((a, b) => {
+      if (a.value === b.value) {
+        if (a.status.confirmed && !b.status.confirmed) {
+          return -1;
+        } else if (!a.status.confirmed && b.status.confirmed) {
+          return 1;
+        } else {
+          return a.status.block_height - b.status.block_height;
+        }
+      }
+      return b.value - a.value;
+    }).slice(0, 500);
+    const maxR = Math.sqrt(sortedUtxos.reduce((max, utxo) => Math.max(max, utxo.value), 0));
+    sortedUtxos.forEach((utxo, index) => {
+      // area proportional to value
+      const r = Math.sqrt(utxo.value);
+
+      // special cases for the first two utxos
+      if (index === 0) {
+        placedCircles.push({ x: 0, y: 0, r, utxo, i: index });
+        return;
+      }
+      if (index === 1) {
+        const c = placedCircles[0];
+        placedCircles.push({ x: c.r + r, y: 0, r, utxo, i: index });
+        sortedInsert(positions, { c1: c, c2: placedCircles[1], d: c.r + r, p: 0 });
+        return;
+      }
+      if (index === 2) {
+        const c = placedCircles[0];
+        placedCircles.push({ x: -c.r - r, y: 0, r, utxo, i: index });
+        sortedInsert(positions, { c1: c, c2: placedCircles[2], d: c.r + r, p: 0 });
+        return;
+      }
+
+      // The best position will be touching two other circles
+      // find the closest such position to the center of the graph
+      // where the circle can be placed without overlapping other circles
+      const numCircles = placedCircles.length;
+      let newCircle: UtxoCircle = null;
+      while (positions.length > 0) {
+        const position = positions.shift();
+        // if the circles are too far apart, skip
+        if (position.d > (position.c1.r + position.c2.r + r + r)) {
+          continue;
+        }
+
+        const { x, y } = intersection(position.c1, position.c2, position.d, r, position.side);
+        if (isNaN(x) || isNaN(y)) {
+          // should never happen
+          continue;
+        }
+
+        // check if the circle would overlap any other circles here
+        let valid = true;
+        const nearbyCircles: { c: UtxoCircle, d: number, s: number }[] = [];
+        for (let k = 0; k < numCircles; k++) {
+          const c = placedCircles[k];
+          if (k === position.c1.i || k === position.c2.i) {
+            nearbyCircles.push({ c, d: c.r + r, s: 0 });
+            continue;
+          }
+          const d = distance(x, y, c.x, c.y);
+          if (d < (r + c.r)) {
+            valid = false;
+            break;
+          } else {
+            nearbyCircles.push({ c, d, s: d - c.r - r });
+          }
+        }
+        if (valid) {
+          newCircle = { x, y, r, utxo, i: index };
+          // add new positions to the candidate list
+          const nearest = nearbyCircles.sort((a, b) => a.s - b.s).slice(0, 5);
+          for (const n of nearest) {
+            if (n.d < (n.c.r + r + maxR + maxR)) {
+              sortedInsert(positions, { c1: newCircle, c2: n.c, d: n.d, p: distance((n.c.x + x) / 2, (n.c.y + y), 0, 0) });
+            }
+          }
+          break;
+        }
+      }
+      if (newCircle) {
+        placedCircles.push(newCircle);
+      } else {
+        // should never happen
+        return;
+      }
+    });
+
+    // Precompute the bounding box of the graph
+    const minX = Math.min(...placedCircles.map(d => d.x - d.r));
+    const maxX = Math.max(...placedCircles.map(d => d.x + d.r));
+    const minY = Math.min(...placedCircles.map(d => d.y - d.r));
+    const maxY = Math.max(...placedCircles.map(d => d.y + d.r));
+    const width = maxX - minX;
+    const height = maxY - minY;
+
+    const data = placedCircles.map((circle) => [
+      circle.utxo.txid + circle.utxo.vout,
+      circle.utxo,
+      circle.x,
+      circle.y,
+      circle.r,
+    ]);
+
+    this.chartOptions = {
+      series: [{
+        type: 'custom',
+        coordinateSystem: undefined,
+        data: data,
+        encode: {
+          itemName: 0,
+          x: 2,
+          y: 3,
+          r: 4,
+        },
+        renderItem: (params, api) => {
+          const chartWidth = api.getWidth();
+          const chartHeight = api.getHeight();
+          const scale = Math.min(chartWidth / width, chartHeight / height);
+          const scaledWidth = width * scale;
+          const scaledHeight = height * scale;
+          const offsetX = (chartWidth - scaledWidth) / 2 - minX * scale;
+          const offsetY = (chartHeight - scaledHeight) / 2 - minY * scale;
+
+          const datum = data[params.dataIndex];
+          const utxo = datum[1] as Utxo;
+          const x = datum[2] as number;
+          const y = datum[3] as number;
+          const r = datum[4] as number;
+          if (r * scale < 2) {
+            // skip items too small to render cleanly
+            return;
+          }
+
+          const valueStr = renderSats(utxo.value, this.stateService.network);
+          const elements: any[] = [
+            {
+              type: 'circle',
+              autoBatch: true,
+              shape: {
+                r: (r * scale) - 1,
+              },
+              style: {
+                fill: '#' + this.getColor(utxo),
+              }
+            },
+          ];
+          const labelFontSize = Math.min(36, r * scale * 0.3);
+          if (labelFontSize > 8) {
+            elements.push({
+              type: 'text',
+              style: {
+                text: valueStr,
+                fontSize: labelFontSize,
+                fill: '#fff',
+                align: 'center',
+                verticalAlign: 'middle',
+              },
+            });
+          }
+          return {
+            type: 'group',
+            x: (x * scale) + offsetX,
+            y: (y * scale) + offsetY,
+            children: elements,
+          };
+        },
+      }],
+      tooltip: {
+        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: (params: any): string => {
+          const utxo = params.data[1] as Utxo;
+          const valueStr = renderSats(utxo.value, this.stateService.network);
+          return `
+          <b style="color: white;">${utxo.txid.slice(0, 6)}...${utxo.txid.slice(-6)}:${utxo.vout}</b>
+          <br>
+          ${valueStr}
+          <br>
+          ${utxo.status.confirmed ? 'Confirmed ' + this.timeService.calculate(utxo.status.block_time, 'since', true, 1, 'minute').text : 'Pending'}
+          `;
+        },
+      }
+    };
+    this.lastUpdate = Date.now();
+
+    this.cd.markForCheck();
+  }
+
+  getColor(utxo: Utxo): string {
+    if (utxo.status.confirmed) {
+      const age = Date.now() / 1000 - utxo.status.block_time;
+      const oneHour = 60 * 60;
+      const fourYears = 4 * 365 * 24 * 60 * 60;
+
+      if (age < oneHour) {
+        return newColorHex;
+      } else if (age >= fourYears) {
+        return oldColorHex;
+      } else {
+        // Logarithmic scale between 1 hour and 4 years
+        const logAge = Math.log(age / oneHour);
+        const logMax = Math.log(fourYears / oneHour);
+        const t = logAge / logMax;
+        return colorToHex(mix(newColor, oldColor, t));
+      }
+    } else {
+      return pendingColorHex;
+    }
+  }
+
+  onChartClick(e): void {
+    if (e.data?.[1]?.txid) {
+      this.zone.run(() => {
+        const url = this.relativeUrlPipe.transform(`/tx/${e.data[1].txid}`);
+        if (e.event.event.shiftKey || e.event.event.ctrlKey || e.event.event.metaKey) {
+          window.open(url + '?mode=details#vout=' + e.data[1].vout);
+        } else {
+          this.router.navigate([url], { fragment: `vout=${e.data[1].vout}` });
+        }
+      });
+    }
+  }
+
+  onChartInit(ec): void {
+    this.chartInstance = ec;
+    this.chartInstance.on('click', 'series', this.onChartClick.bind(this));
+  }
+
+  ngOnDestroy(): void {
+    if (this.subscription) {
+      this.subscription.unsubscribe();
+    }
+    clearInterval(this.updateInterval);
+  }
+
+  isMobile(): boolean {
+    return (window.innerWidth <= 767.98);
+  }
+}
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 12bb96166..cad4b47bf 100644
--- a/frontend/src/app/docs/api-docs/api-docs-data.ts
+++ b/frontend/src/app/docs/api-docs/api-docs-data.ts
@@ -9163,11 +9163,13 @@ export const restApiDocsData = [
       Filters can be applied:<ul>
       <li><code>status</code>: <code>all</code>, <code>requested</code>, <code>accelerating</code>, <code>mined</code>, <code>completed</code>, <code>failed</code></li>
       <li><code>timeframe</code>: <code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>, <code>4y</code>, <code>all</code></li>
-      <li><code>poolUniqueId</code>: any id from <a target="_blank" href="https://github.com/mempool/mining-pools/blob/master/pools-v2.json">https://github.com/mempool/mining-pools/blob/master/pools-v2.json</a>. <i>Note: This will return all acceleration requests accepted by the pool but the the listed transactions may have been mined by another pool.</i>
+      <li><code>minedByPoolUniqueId</code>: any id from <a target="_blank" href="https://github.com/mempool/mining-pools/blob/master/pools-v2.json">pools-v2.json</a>
       <li><code>blockHash</code>: a block hash</a>
       <li><code>blockHeight</code>: a block height</a>
       <li><code>page</code>: the requested page number if using pagination <i>(min: 1)</i></a>
       <li><code>pageLength</code>: the page lenght if using pagination <i>(min: 1, max: 50)</i></a>
+      <li><code>from</code>: unix timestamp (<i>overrides <code>timeframe</code></i>)</a>
+      <li><code>to</code>: unix timestamp (<i>overrides <code>timeframe</code></i>)</a>
       </ul></p>`
     },
     urlString: "/v1/services/accelerator/accelerations/history",
@@ -9187,21 +9189,22 @@ export const restApiDocsData = [
           headers: '',
           response: `[
   {
-    "txid": "d7e1796d8eb4a09d4e6c174e36cfd852f1e6e6c9f7df4496339933cd32cbdd1d",
-    "status": "completed",
-    "added": 1707421053,
-    "lastUpdated": 1719134667,
-    "effectiveFee": 146,
-    "effectiveVsize": 141,
-    "feeDelta": 14000,
-    "blockHash": "00000000000000000000482f0746d62141694b9210a813b97eb8445780a32003",
-    "blockHeight": 829559,
-    "bidBoost": 3239,
-    "boostVersion": "v1",
+    "txid": "f829900985aad885c13fb90555d27514b05a338202c7ef5d694f4813ad474487",
+    "status": "completed_provisional",
+    "added": 1728111527,
+    "lastUpdated": 1728112113,
+    "effectiveFee": 1385,
+    "effectiveVsize": 276,
+    "feeDelta": 3000,
+    "blockHash": "00000000000000000000cde89e34036ece454ca2d07ddd7f71ab46307ca87423",
+    "blockHeight": 864248,
+    "bidBoost": 65,
+    "boostVersion": "v2",
     "pools": [
-      111
+      111,
+      115,
     ],
-    "minedByPoolUniqueId": 111
+    "minedByPoolUniqueId": 115
   }
 ]`,
         },
diff --git a/frontend/src/app/graphs/echarts.ts b/frontend/src/app/graphs/echarts.ts
index 74fec1e71..67ed7e3b8 100644
--- a/frontend/src/app/graphs/echarts.ts
+++ b/frontend/src/app/graphs/echarts.ts
@@ -1,6 +1,6 @@
 // Import tree-shakeable echarts
 import * as echarts from 'echarts/core';
-import { LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart } from 'echarts/charts';
+import { LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart, CustomChart } from 'echarts/charts';
 import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, GeoComponent, DataZoomComponent, VisualMapComponent, MarkLineComponent } from 'echarts/components';
 import { SVGRenderer, CanvasRenderer } from 'echarts/renderers';
 // Typescript interfaces
@@ -12,6 +12,7 @@ echarts.use([
   TitleComponent, TooltipComponent, GridComponent,
   LegendComponent, GeoComponent, DataZoomComponent,
   VisualMapComponent, MarkLineComponent,
-  LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart
+  LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart, GaugeChart,
+  CustomChart,
 ]);
 export { echarts, EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption };
\ No newline at end of file
diff --git a/frontend/src/app/graphs/graphs.module.ts b/frontend/src/app/graphs/graphs.module.ts
index de048fd2d..ee51069c5 100644
--- a/frontend/src/app/graphs/graphs.module.ts
+++ b/frontend/src/app/graphs/graphs.module.ts
@@ -36,6 +36,7 @@ import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools
 import { BlockHealthGraphComponent } from '../components/block-health-graph/block-health-graph.component';
 import { AddressComponent } from '../components/address/address.component';
 import { AddressGraphComponent } from '../components/address-graph/address-graph.component';
+import { UtxoGraphComponent } from '../components/utxo-graph/utxo-graph.component';
 import { ActiveAccelerationBox } from '../components/acceleration/active-acceleration-box/active-acceleration-box.component';
 import { CommonModule } from '@angular/common';
 
@@ -76,6 +77,7 @@ import { CommonModule } from '@angular/common';
     HashrateChartPoolsComponent,
     BlockHealthGraphComponent,
     AddressGraphComponent,
+    UtxoGraphComponent,
     ActiveAccelerationBox,
   ],
   imports: [
diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts
index b32a2aae6..5bc5bfc1d 100644
--- a/frontend/src/app/interfaces/electrs.interface.ts
+++ b/frontend/src/app/interfaces/electrs.interface.ts
@@ -233,3 +233,10 @@ interface AssetStats {
   peg_out_amount: number;
   burn_count: number;
 }
+
+export interface Utxo {
+  txid: string;
+  vout: number;
+  value: number;
+  status: Status;
+}
\ No newline at end of file
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts
index 4d2ffc09a..4c7796590 100644
--- a/frontend/src/app/interfaces/node-api.interface.ts
+++ b/frontend/src/app/interfaces/node-api.interface.ts
@@ -203,6 +203,7 @@ export interface BlockExtension {
     id: number;
     name: string;
     slug: string;
+    minerNames: string[] | null;
   }
 }
 
@@ -239,7 +240,7 @@ export interface TransactionStripped {
   acc?: boolean;
   flags?: number | null;
   time?: number;
-  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'added_prioritized' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
+  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'added_prioritized' | 'prioritized' | 'added_deprioritized' | 'deprioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
   context?: 'projected' | 'actual';
 }
 
diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts
index 35e0ffa09..7552224f5 100644
--- a/frontend/src/app/interfaces/websocket.interface.ts
+++ b/frontend/src/app/interfaces/websocket.interface.ts
@@ -72,11 +72,13 @@ export interface MempoolBlockWithTransactions extends MempoolBlock {
 }
 
 export interface MempoolBlockDelta {
+  block: number;
   added: TransactionStripped[];
   removed: string[];
   changed: { txid: string, rate: number, flags: number, acc: boolean }[];
 }
 export interface MempoolBlockState {
+  block: number;
   transactions: TransactionStripped[];
 }
 export type MempoolBlockUpdate = MempoolBlockDelta | MempoolBlockState;
diff --git a/frontend/src/app/route-guards.ts b/frontend/src/app/route-guards.ts
index 4808713c1..7ed44176a 100644
--- a/frontend/src/app/route-guards.ts
+++ b/frontend/src/app/route-guards.ts
@@ -13,7 +13,8 @@ class GuardService {
 
   trackerGuard(route: Route, segments: UrlSegment[]): boolean {
     const preferredRoute = this.router.getCurrentNavigation()?.extractedUrl.queryParams?.mode;
-    return (preferredRoute === 'status' || (preferredRoute !== 'details' && this.navigationService.isInitialLoad())) && window.innerWidth <= 767.98;
+    const path = this.router.getCurrentNavigation()?.extractedUrl.root.children.primary.segments;
+    return (preferredRoute === 'status' || (preferredRoute !== 'details' && this.navigationService.isInitialLoad())) && window.innerWidth <= 767.98 && !(path.length === 2 && ['push', 'test'].includes(path[1].path));
   }
 }
 
diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts
index 7faaea87c..8e991782b 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 { BehaviorSubject, Observable, catchError, filter, from, of, shareReplay, switchMap, take, tap } from 'rxjs';
-import { Transaction, Address, Outspend, Recent, Asset, ScriptHash, AddressTxSummary } from '../interfaces/electrs.interface';
+import { Transaction, Address, Outspend, Recent, Asset, ScriptHash, AddressTxSummary, Utxo } from '../interfaces/electrs.interface';
 import { StateService } from './state.service';
 import { BlockExtended } from '../interfaces/node-api.interface';
 import { calcScriptHash$ } from '../bitcoin.utils';
@@ -166,6 +166,16 @@ export class ElectrsApiService {
     );
   }
 
+  getAddressUtxos$(address: string): Observable<Utxo[]> {
+    return this.httpClient.get<Utxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/utxo');
+  }
+
+  getScriptHashUtxos$(script: string): Observable<Utxo[]> {
+    return from(calcScriptHash$(script)).pipe(
+      switchMap(scriptHash => this.httpClient.get<Utxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/scripthash/' + scriptHash + '/utxo')),
+    );
+  }
+
   getAsset$(assetId: string): Observable<Asset> {
     return this.httpClient.get<Asset>(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId);
   }
diff --git a/frontend/src/app/services/eta.service.ts b/frontend/src/app/services/eta.service.ts
index f632c9adb..6834237b6 100644
--- a/frontend/src/app/services/eta.service.ts
+++ b/frontend/src/app/services/eta.service.ts
@@ -28,7 +28,7 @@ export class EtaService {
     return combineLatest([
       this.stateService.mempoolTxPosition$.pipe(map(p => p?.position)),
       this.stateService.difficultyAdjustment$,
-      miningStats ? of(miningStats) : this.miningService.getMiningStats('1w'),
+      miningStats ? of(miningStats) : this.miningService.getMiningStats('1m'),
     ]).pipe(
       map(([mempoolPosition, da, miningStats]) => {
         if (!mempoolPosition || !estimate?.pools?.length || !miningStats || !da) {
@@ -166,7 +166,7 @@ export class EtaService {
         pools[pool.poolUniqueId] = pool;
       }
       const unacceleratedPosition = this.mempoolPositionFromFees(getUnacceleratedFeeRate(tx, true), mempoolBlocks);
-      const totalAcceleratedHashrate = accelerationPositions.reduce((total, pos) => total + (pools[pos.poolId].lastEstimatedHashrate), 0);
+      const totalAcceleratedHashrate = accelerationPositions.reduce((total, pos) => total + (pools[pos.poolId]?.lastEstimatedHashrate || 0), 0);
       const shares = [
         {
           block: unacceleratedPosition.block,
@@ -174,7 +174,7 @@ export class EtaService {
         },
         ...accelerationPositions.map(pos => ({
           block: pos.block,
-          hashrateShare: ((pools[pos.poolId].lastEstimatedHashrate) / miningStats.lastEstimatedHashrate)
+          hashrateShare: ((pools[pos.poolId]?.lastEstimatedHashrate || 0) / miningStats.lastEstimatedHashrate)
         }))
       ];
       return this.calculateETAFromShares(shares, da);
@@ -204,7 +204,7 @@ export class EtaService {
 
       let tailProb = 0;
       let Q = 0;
-      for (let i = 0; i < max; i++) {
+      for (let i = 0; i <= max; i++) {
         // find H_i
         const H = shares.reduce((total, share) => total + (share.block <= i ? share.hashrateShare : 0), 0);
         // find S_i
@@ -215,7 +215,7 @@ export class EtaService {
         tailProb += S;
       }
       // at max depth, the transaction is guaranteed to be mined in the next block if it hasn't already
-      Q += (1-tailProb);
+      Q += ((max + 1) * (1-tailProb));
       const eta = da.timeAvg * Q; // T x Q
 
       return {
diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts
index 1366342f7..c87044781 100644
--- a/frontend/src/app/services/services-api.service.ts
+++ b/frontend/src/app/services/services-api.service.ts
@@ -4,7 +4,7 @@ import { HttpClient } from '@angular/common/http';
 import { StateService } from './state.service';
 import { StorageService } from './storage.service';
 import { MenuGroup } from '../interfaces/services.interface';
-import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap } from 'rxjs';
+import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap, map } from 'rxjs';
 import { IBackendInfo } from '../interfaces/websocket.interface';
 import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
 import { AccelerationStats } from '../components/acceleration/acceleration-stats/acceleration-stats.component';
@@ -160,6 +160,29 @@ export class ServicesApiServices {
     return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations/history`, { params: { ...params } });
   }
 
+  getAllAccelerationHistory$(params: AccelerationHistoryParams, limit?: number, findTxid?: string): Observable<Acceleration[]> {
+    const getPage$ = (page: number, accelerations: Acceleration[] = []): Observable<{ page: number, total: number, accelerations: Acceleration[] }> => {
+      return this.getAccelerationHistoryObserveResponse$({...params, page}).pipe(
+        map((response) => ({
+          page,
+          total: parseInt(response.headers.get('X-Total-Count'), 10) || 0,
+          accelerations: accelerations.concat(response.body || []),
+        })),
+        switchMap(({page, total, accelerations}) => {
+          if (accelerations.length >= Math.min(total, limit ?? Infinity) || (findTxid && accelerations.find((acc) => acc.txid === findTxid))) {
+            return of({ page, total, accelerations });
+          } else {
+            return getPage$(page + 1, accelerations);
+          }
+        }),
+      );
+    };
+
+    return getPage$(1).pipe(
+      map(({ accelerations }) => accelerations),
+    );
+  }
+
   getAccelerationHistoryObserveResponse$(params: AccelerationHistoryParams): Observable<any> {
     return this.httpClient.get<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations/history`, { params: { ...params }, observe: 'response'});
   }
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index 365c1daa2..13ffc7fc5 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -5,7 +5,7 @@ import { AccelerationDelta, HealthCheckHost, IBackendInfo, MempoolBlock, Mempool
 import { Acceleration, AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '../interfaces/node-api.interface';
 import { Router, NavigationStart } from '@angular/router';
 import { isPlatformBrowser } from '@angular/common';
-import { filter, map, scan, shareReplay } from 'rxjs/operators';
+import { filter, map, scan, share, shareReplay } from 'rxjs/operators';
 import { StorageService } from './storage.service';
 import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
 import { ActiveFilter } from '../shared/filters.utils';
@@ -131,6 +131,7 @@ export class StateService {
   latestBlockHeight = -1;
   blocks: BlockExtended[] = [];
   mempoolSequence: number;
+  mempoolBlockState: { block: number, transactions: { [txid: string]: TransactionStripped} };
 
   backend$ = new BehaviorSubject<'esplora' | 'electrum' | 'none'>('esplora');
   networkChanged$ = new ReplaySubject<string>(1);
@@ -143,7 +144,7 @@ export class StateService {
   mempoolInfo$ = new ReplaySubject<MempoolInfo>(1);
   mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
   mempoolBlockUpdate$ = new Subject<MempoolBlockUpdate>();
-  liveMempoolBlockTransactions$: Observable<{ [txid: string]: TransactionStripped}>;
+  liveMempoolBlockTransactions$: Observable<{ block: number, transactions: { [txid: string]: TransactionStripped} }>;
   accelerations$ = new Subject<AccelerationDelta>();
   liveAccelerations$: Observable<Acceleration[]>;
   txConfirmed$ = new Subject<[string, BlockExtended]>();
@@ -231,29 +232,40 @@ export class StateService {
       }
     });
 
-    this.liveMempoolBlockTransactions$ = this.mempoolBlockUpdate$.pipe(scan((transactions: { [txid: string]: TransactionStripped }, change: MempoolBlockUpdate): { [txid: string]: TransactionStripped } => {
+    this.liveMempoolBlockTransactions$ = this.mempoolBlockUpdate$.pipe(scan((acc: { block: number, transactions: { [txid: string]: TransactionStripped } }, change: MempoolBlockUpdate): { block: number, transactions: { [txid: string]: TransactionStripped } } => {
       if (isMempoolState(change)) {
         const txMap = {};
         change.transactions.forEach(tx => {
           txMap[tx.txid] = tx;
         });
-        return txMap;
+        this.mempoolBlockState = {
+          block: change.block,
+          transactions: txMap
+        };
+        return this.mempoolBlockState;
       } else {
         change.added.forEach(tx => {
-          transactions[tx.txid] = tx;
+          acc.transactions[tx.txid] = tx;
         });
         change.removed.forEach(txid => {
-          delete transactions[txid];
+          delete acc.transactions[txid];
         });
         change.changed.forEach(tx => {
-          if (transactions[tx.txid]) {
-            transactions[tx.txid].rate = tx.rate;
-            transactions[tx.txid].acc = tx.acc;
+          if (acc.transactions[tx.txid]) {
+            acc.transactions[tx.txid].rate = tx.rate;
+            acc.transactions[tx.txid].acc = tx.acc;
           }
         });
-        return transactions;
+        this.mempoolBlockState = {
+          block: change.block,
+          transactions: acc.transactions
+        };
+        return this.mempoolBlockState;
       }
-    }, {}));
+    }, {}),
+    share()
+    );
+    this.liveMempoolBlockTransactions$.subscribe();
 
     // Emits the full list of pending accelerations each time it changes
     this.liveAccelerations$ = this.accelerations$.pipe(
diff --git a/frontend/src/app/services/time.service.ts b/frontend/src/app/services/time.service.ts
new file mode 100644
index 000000000..6f7978774
--- /dev/null
+++ b/frontend/src/app/services/time.service.ts
@@ -0,0 +1,266 @@
+import { Injectable } from '@angular/core';
+import { DatePipe } from '@angular/common';
+import { dates } from '../shared/i18n/dates';
+
+const intervals = {
+  year: 31536000,
+  month: 2592000,
+  week: 604800,
+  day: 86400,
+  hour: 3600,
+  minute: 60,
+  second: 1
+};
+
+const precisionThresholds = {
+  year: 100,
+  month: 18,
+  week: 12,
+  day: 31,
+  hour: 48,
+  minute: 90,
+  second: 90
+};
+
+@Injectable({
+  providedIn: 'root'
+})
+export class TimeService {
+
+  constructor(private datePipe: DatePipe) {}
+
+  calculate(
+    time: number,
+    kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within',
+    relative: boolean = false,
+    precision: number = 0,
+    minUnit: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' = 'second',
+    showTooltip: boolean = false,
+    units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
+    dateString?: string,
+    lowercaseStart: boolean = false,
+    numUnits: number = 1,
+    fractionDigits: number = 0,
+  ): { text: string, tooltip: string } {
+    if (time == null) {
+      return { text: '', tooltip: '' };
+    }
+
+    let seconds: number;
+    let tooltip: string = '';
+    switch (kind) {
+      case 'since':
+        seconds = Math.floor((+new Date() - +new Date(dateString || time * 1000)) / 1000);
+        tooltip = this.datePipe.transform(new Date(dateString || time * 1000), 'yyyy-MM-dd HH:mm') || '';
+        break;
+      case 'until':
+      case 'within':
+        seconds = (+new Date(time) - +new Date()) / 1000;
+        tooltip = this.datePipe.transform(new Date(time), 'yyyy-MM-dd HH:mm') || '';
+        break;
+      default:
+        seconds = Math.floor(time);
+        tooltip = '';
+    }
+
+    if (!showTooltip || relative) {
+      tooltip = '';
+    }
+
+    if (seconds < 1 && kind === 'span') {
+      return { tooltip, text: $localize`:@@date-base.immediately:Immediately` };
+    } else if (seconds < 60) {
+      if (relative || kind === 'since') {
+        if (lowercaseStart) {
+          return { tooltip, text: $localize`:@@date-base.just-now:Just now`.charAt(0).toLowerCase() + $localize`:@@date-base.just-now:Just now`.slice(1) };
+        }
+        return { tooltip, text: $localize`:@@date-base.just-now:Just now` };
+      } else if (kind === 'until' || kind === 'within') {
+        seconds = 60;
+      }
+    }
+
+    let counter: number;
+    const result: string[] = [];
+    let usedUnits = 0;
+    for (const [index, unit] of units.entries()) {
+      let precisionUnit = units[Math.min(units.length - 1, index + precision)];
+      counter = Math.floor(seconds / intervals[unit]);
+      const precisionCounter = Math.round(seconds / intervals[precisionUnit]);
+      if (precisionCounter > precisionThresholds[precisionUnit]) {
+        precisionUnit = unit;
+      }
+      if (units.indexOf(precisionUnit) === units.indexOf(minUnit)) {
+        counter = Math.max(1, counter);
+      }
+      if (counter > 0) {
+        let rounded;
+        const roundFactor = Math.pow(10,fractionDigits || 0);
+        if ((kind === 'until' || kind === 'within') && usedUnits < numUnits) {
+          rounded = Math.floor((seconds / intervals[precisionUnit]) * roundFactor) / roundFactor;
+        } else {
+          rounded = Math.round((seconds / intervals[precisionUnit]) * roundFactor) / roundFactor;
+        }
+        if ((kind !== 'until' && kind !== 'within')|| numUnits === 1) {
+          return { tooltip, text: this.formatTime(kind, precisionUnit, rounded) };
+        } else {
+          if (!usedUnits) {
+            result.push(this.formatTime(kind, precisionUnit, rounded));
+          } else {
+            result.push(this.formatTime('', precisionUnit, rounded));
+          }
+          seconds -= (rounded * intervals[precisionUnit]);
+          usedUnits++;
+          if (usedUnits >= numUnits) {
+            return { tooltip, text: result.join(', ') };
+          }
+        }
+      }
+    }
+    return { tooltip, text: result.join(', ') };
+  }
+
+  private formatTime(kind, unit, number): string {
+    const dateStrings = dates(number);
+    switch (kind) {
+      case 'since':
+        if (number === 1) {
+          switch (unit) { // singular (1 day)
+            case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
+            case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
+            case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
+            case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break;
+            case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break;
+            case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; break;
+            case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
+          }
+        } else {
+          switch (unit) { // plural (2 days)
+            case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
+            case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
+            case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
+            case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break;
+            case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break;
+            case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; break;
+            case 'second': return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; break;
+          }
+        }
+        break;
+      case 'until':
+        if (number === 1) {
+          switch (unit) { // singular (In ~1 day)
+            case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
+            case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
+            case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
+            case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break;
+            case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break;
+            case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`;
+            case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
+          }
+        } else {
+          switch (unit) { // plural (In ~2 days)
+            case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
+            case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
+            case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
+            case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break;
+            case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break;
+            case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; break;
+            case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; break;
+          }
+        }
+        break;
+      case 'within':
+        if (number === 1) {
+          switch (unit) { // singular (In ~1 day)
+            case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYear}:DATE:`; break;
+            case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonth}:DATE:`; break;
+            case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeek}:DATE:`; break;
+            case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDay}:DATE:`; break;
+            case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHour}:DATE:`; break;
+            case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinute}:DATE:`;
+            case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSecond}:DATE:`;
+          }
+        } else {
+          switch (unit) { // plural (In ~2 days)
+            case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYears}:DATE:`; break;
+            case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonths}:DATE:`; break;
+            case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeeks}:DATE:`; break;
+            case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDays}:DATE:`; break;
+            case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHours}:DATE:`; break;
+            case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinutes}:DATE:`; break;
+            case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSeconds}:DATE:`; break;
+          }
+        }
+        break;
+      case 'span':
+        if (number === 1) {
+          switch (unit) { // singular (1 day)
+            case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
+            case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
+            case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
+            case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break;
+            case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break;
+            case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; break;
+            case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
+          }
+        } else {
+          switch (unit) { // plural (2 days)
+            case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
+            case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
+            case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
+            case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break;
+            case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break;
+            case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; break;
+            case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; break;
+          }
+        }
+        break;
+      case 'before':
+      if (number === 1) {
+        switch (unit) { // singular (1 day)
+          case 'year': return $localize`:@@time-before:${dateStrings.i18nYear}:DATE: before`; break;
+          case 'month': return $localize`:@@time-before:${dateStrings.i18nMonth}:DATE: before`; break;
+          case 'week': return $localize`:@@time-before:${dateStrings.i18nWeek}:DATE: before`; break;
+          case 'day': return $localize`:@@time-before:${dateStrings.i18nDay}:DATE: before`; break;
+          case 'hour': return $localize`:@@time-before:${dateStrings.i18nHour}:DATE: before`; break;
+          case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinute}:DATE: before`; break;
+          case 'second': return $localize`:@@time-before:${dateStrings.i18nSecond}:DATE: before`; break;
+        }
+      } else {
+        switch (unit) { // plural (2 days)
+          case 'year': return $localize`:@@time-before:${dateStrings.i18nYears}:DATE: before`; break;
+          case 'month': return $localize`:@@time-before:${dateStrings.i18nMonths}:DATE: before`; break;
+          case 'week': return $localize`:@@time-before:${dateStrings.i18nWeeks}:DATE: before`; break;
+          case 'day': return $localize`:@@time-before:${dateStrings.i18nDays}:DATE: before`; break;
+          case 'hour': return $localize`:@@time-before:${dateStrings.i18nHours}:DATE: before`; break;
+          case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinutes}:DATE: before`; break;
+          case 'second': return $localize`:@@time-before:${dateStrings.i18nSeconds}:DATE: before`; break;
+        }
+      }
+      break;
+      default:
+        if (number === 1) {
+          switch (unit) { // singular (1 day)
+            case 'year': return dateStrings.i18nYear; break;
+            case 'month': return dateStrings.i18nMonth; break;
+            case 'week': return dateStrings.i18nWeek; break;
+            case 'day': return dateStrings.i18nDay; break;
+            case 'hour': return dateStrings.i18nHour; break;
+            case 'minute': return dateStrings.i18nMinute; break;
+            case 'second': return dateStrings.i18nSecond; break;
+          }
+        } else {
+          switch (unit) { // plural (2 days)
+            case 'year': return dateStrings.i18nYears; break;
+            case 'month': return dateStrings.i18nMonths; break;
+            case 'week': return dateStrings.i18nWeeks; break;
+            case 'day': return dateStrings.i18nDays; break;
+            case 'hour': return dateStrings.i18nHours; break;
+            case 'minute': return dateStrings.i18nMinutes; break;
+            case 'second': return dateStrings.i18nSeconds; break;
+          }
+        }
+    }
+    return '';
+  }
+}
diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts
index fd67ddb2e..39e9d1af3 100644
--- a/frontend/src/app/services/websocket.service.ts
+++ b/frontend/src/app/services/websocket.service.ts
@@ -35,6 +35,7 @@ export class WebsocketService {
   private isTrackingAddresses: string[] | false = false;
   private isTrackingAccelerations: boolean = false;
   private trackingMempoolBlock: number;
+  private stoppingTrackMempoolBlock: any | null = null;
   private latestGitCommit = '';
   private onlineCheckTimeout: number;
   private onlineCheckTimeoutTwo: number;
@@ -203,19 +204,31 @@ export class WebsocketService {
     this.websocketSubject.next({ 'track-asset': 'stop' });
   }
 
-  startTrackMempoolBlock(block: number, force: boolean = false) {
+  startTrackMempoolBlock(block: number, force: boolean = false): boolean {
+    if (this.stoppingTrackMempoolBlock) {
+      clearTimeout(this.stoppingTrackMempoolBlock);
+    }
     // skip duplicate tracking requests
     if (force || this.trackingMempoolBlock !== block) {
       this.websocketSubject.next({ 'track-mempool-block': block });
       this.isTrackingMempoolBlock = true;
       this.trackingMempoolBlock = block;
+      return true;
     }
+    return false;
   }
 
-  stopTrackMempoolBlock() {
-    this.websocketSubject.next({ 'track-mempool-block': -1 });
+  stopTrackMempoolBlock(): void {
+    if (this.stoppingTrackMempoolBlock) {
+      clearTimeout(this.stoppingTrackMempoolBlock);
+    }
     this.isTrackingMempoolBlock = false;
-    this.trackingMempoolBlock = null;
+    this.stoppingTrackMempoolBlock = setTimeout(() => {
+      this.stoppingTrackMempoolBlock = null;
+      this.websocketSubject.next({ 'track-mempool-block': -1 });
+      this.trackingMempoolBlock = null;
+      this.stateService.mempoolBlockState = null;
+    }, 2000);
   }
 
   startTrackRbf(mode: 'all' | 'fullRbf') {
@@ -424,6 +437,7 @@ export class WebsocketService {
         if (response['projected-block-transactions'].blockTransactions) {
           this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
           this.stateService.mempoolBlockUpdate$.next({
+            block: this.trackingMempoolBlock,
             transactions: response['projected-block-transactions'].blockTransactions.map(uncompressTx),
           });
         } else if (response['projected-block-transactions'].delta) {
@@ -432,7 +446,7 @@ export class WebsocketService {
             this.startTrackMempoolBlock(this.trackingMempoolBlock, true);
           } else {
             this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
-            this.stateService.mempoolBlockUpdate$.next(uncompressDeltaChange(response['projected-block-transactions'].delta));
+            this.stateService.mempoolBlockUpdate$.next(uncompressDeltaChange(this.trackingMempoolBlock, response['projected-block-transactions'].delta));
           }
         }
       }
diff --git a/frontend/src/app/shared/address-utils.ts b/frontend/src/app/shared/address-utils.ts
index 92646af14..59c85014b 100644
--- a/frontend/src/app/shared/address-utils.ts
+++ b/frontend/src/app/shared/address-utils.ts
@@ -17,6 +17,7 @@ export type AddressType = 'fee'
   | 'v0_p2wsh'
   | 'v1_p2tr'
   | 'confidential'
+  | 'anchor'
   | 'unknown'
 
 const ADDRESS_PREFIXES = {
@@ -188,6 +189,12 @@ export class AddressTypeInfo {
         const v = vin[0];
         this.processScript(new ScriptInfo('scriptpubkey', v.prevout.scriptpubkey, v.prevout.scriptpubkey_asm));
       }
+    } else if (this.type === 'unknown') {
+      for (const v of vin) {
+        if (v.prevout?.scriptpubkey === '51024e73') {
+          this.type = 'anchor';
+        }
+      }
     }
     // and there's nothing more to learn from processing inputs for other types
   }
@@ -197,6 +204,10 @@ export class AddressTypeInfo {
       if (!this.scripts.size) {
         this.processScript(new ScriptInfo('scriptpubkey', output.scriptpubkey, output.scriptpubkey_asm));
       }
+    } else if (this.type === 'unknown') {
+      if (output.scriptpubkey === '51024e73') {
+        this.type = 'anchor';
+      }
     }
   }
 
diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts
index 697b11b5e..5ccb369f6 100644
--- a/frontend/src/app/shared/common.utils.ts
+++ b/frontend/src/app/shared/common.utils.ts
@@ -1,5 +1,7 @@
 import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed } from "../interfaces/websocket.interface";
 import { TransactionStripped } from "../interfaces/node-api.interface";
+import { AmountShortenerPipe } from "./pipes/amount-shortener.pipe";
+const amountShortenerPipe = new AmountShortenerPipe();
 
 export function isMobile(): boolean {
   return (window.innerWidth <= 767.98);
@@ -170,8 +172,9 @@ export function uncompressTx(tx: TransactionCompressed): TransactionStripped {
   };
 }
 
-export function uncompressDeltaChange(delta: MempoolBlockDeltaCompressed): MempoolBlockDelta {
+export function uncompressDeltaChange(block: number, delta: MempoolBlockDeltaCompressed): MempoolBlockDelta {
   return {
+    block,
     added: delta.added.map(uncompressTx),
     removed: delta.removed,
     changed: delta.changed.map(tx => ({
@@ -183,6 +186,33 @@ export function uncompressDeltaChange(delta: MempoolBlockDeltaCompressed): Mempo
   };
 }
 
+export function renderSats(value: number, network: string, mode: 'sats' | 'btc' | 'auto' = 'auto'): string {
+  let prefix = '';
+  switch (network) {
+    case 'liquid':
+      prefix = 'L';
+      break;
+    case 'liquidtestnet':
+      prefix = 'tL';
+      break;
+    case 'testnet':
+    case 'testnet4':
+      prefix = 't';
+      break;
+    case 'signet':
+      prefix = 's';
+      break;
+  }
+  if (mode === 'btc' || (mode === 'auto' && value >= 1000000)) {
+    return `${amountShortenerPipe.transform(value / 100000000, 2)} ${prefix}BTC`;
+  } else {
+    if (prefix.length) {
+      prefix += '-';
+    }
+    return `${amountShortenerPipe.transform(value, 2)} ${prefix}sats`;
+  }
+}
+
 export function insecureRandomUUID(): string {
   const hexDigits = '0123456789abcdef';
   const uuidLengths = [8, 4, 4, 4, 12];
diff --git a/frontend/src/app/shared/components/address-type/address-type.component.html b/frontend/src/app/shared/components/address-type/address-type.component.html
index fe4286689..598c21a6e 100644
--- a/frontend/src/app/shared/components/address-type/address-type.component.html
+++ b/frontend/src/app/shared/components/address-type/address-type.component.html
@@ -20,6 +20,9 @@
   @case ('multisig') {
     <span i18n="address.bare-multisig">bare multisig</span>
   }
+  @case ('anchor') {
+    <span>anchor</span>
+  }
   @case (null) {
     <span>unknown</span>
   }
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 a2e7286e0..6e0e46300 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
@@ -13,8 +13,13 @@
         </div>
         @if (!enterpriseInfo?.footer_img) {
           <p class="explore-tagline-mobile">
-            <ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
-            <ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>
+            @if (officialMempoolSpace) {
+              <ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
+              <ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>  
+            } @else {
+              <ng-container i18n="shared.be-your-own-explorer">Be your own explorer</ng-container>
+              <ng-template [ngIf]="locale.substr(0, 2) === 'en'">&trade;</ng-template>
+            }
           </p>
         }
         <div class="site-options language-selector d-flex justify-content-center align-items-center" [class]="{'services': isServicesPage}">
@@ -27,29 +32,38 @@
           <div class="selector">
             <app-rate-unit-selector></app-rate-unit-selector>
           </div>
+          <div class="selector d-none" [ngClass]="isServicesPage ? 'd-lg-flex' : 'd-md-flex'">
+            <app-amount-selector></app-amount-selector>
+          </div>
           @if (!env.customize?.theme) {
-            <div class="selector d-none d-sm-flex">
+            <div class="selector d-none" [ngClass]="isServicesPage ? 'd-lg-flex' : 'd-md-flex'">
               <app-theme-selector></app-theme-selector>
             </div>
           }
-          <a *ngIf="stateService.isMempoolSpaceBuild" class="btn btn-purple sponsor d-none d-sm-flex justify-content-center" [routerLink]="['/login']">
+          <a *ngIf="stateService.isMempoolSpaceBuild" class="btn btn-purple sponsor d-none justify-content-center" [ngClass]="isServicesPage ? 'd-lg-flex' : 'd-md-flex'" [routerLink]="['/login']">
             <span *ngIf="user" i18n="shared.my-account" class="nowrap">My Account</span>
             <span *ngIf="!user" i18n="shared.sign-in" class="nowrap">Sign In</span>
           </a>
         </div>
         @if (!env.customize?.theme) {
-          <div class="selector d-flex d-sm-none justify-content-center ml-auto mr-auto mt-0">
-            <app-theme-selector></app-theme-selector>
+          <div class="selector d-flex justify-content-center ml-auto mr-auto mt-0" [ngClass]="isServicesPage ? 'd-lg-none' : 'd-md-none'">
+            <app-amount-selector class="add-margin"></app-amount-selector>
+            <app-theme-selector class="add-margin"></app-theme-selector>
           </div>
         }
         @if (!enterpriseInfo?.footer_img) {
-          <a *ngIf="stateService.isMempoolSpaceBuild" class="btn btn-purple sponsor d-flex d-sm-none justify-content-center ml-auto mr-auto mt-0 mb-2" [routerLink]="['/login']">
+          <a *ngIf="stateService.isMempoolSpaceBuild" class="btn btn-purple sponsor d-flex justify-content-center ml-auto mr-auto mt-0 mb-2" [ngClass]="isServicesPage ? 'd-lg-none' : 'd-md-none'" [routerLink]="['/login']">
             <span *ngIf="user" i18n="shared.my-account" class="nowrap">My Account</span>
             <span *ngIf="!user" i18n="shared.sign-in" class="nowrap">Sign In</span>
           </a>
           <p class="explore-tagline-desktop">
-            <ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
-            <ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>
+            @if (officialMempoolSpace) {
+              <ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container>
+              <ng-template [ngIf]="locale.substr(0, 2) === 'en'">&reg;</ng-template>  
+            } @else {
+              <ng-container i18n="shared.be-your-own-explorer">Be your own explorer</ng-container>
+              <ng-template [ngIf]="locale.substr(0, 2) === 'en'">&trade;</ng-template>
+            }
           </p>
         }
       </div>
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 e0daf4f4c..bf47d5489 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
@@ -76,6 +76,11 @@ footer .selector {
   display: inline-block;
 }
 
+footer .add-margin {
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
 footer .row.link-tree {
   max-width: 1140px;
   margin: 0 auto;
@@ -154,7 +159,7 @@ footer .nowrap {
   display: block;
 }
 
-@media (min-width: 951px) {
+@media (min-width: 1020px) {
   :host-context(.ltr-layout) .language-selector {
     float: right !important;
   }
@@ -172,7 +177,24 @@ footer .nowrap {
 }
 
 .services {
-  @media (min-width: 951px) and (max-width: 1147px) {
+  @media (min-width: 1300px) {
+    :host-context(.ltr-layout) .language-selector {
+      float: right !important;
+    }
+    :host-context(.rtl-layout) .language-selector {
+      float: left !important;
+    }
+  
+    .explore-tagline-desktop {
+      display: block;
+    }
+  
+    .explore-tagline-mobile {
+      display: none;
+    }
+  }
+
+  @media (max-width: 1300px) {
     :host-context(.ltr-layout) .services .language-selector {
       float: none !important;
     }
@@ -248,7 +270,7 @@ footer .nowrap {
 
 }
 
-@media (max-width: 950px) {
+@media (max-width: 1019px) {
 
   .main-logo {
     width: 220px;
@@ -287,7 +309,7 @@ footer .nowrap {
   }
 }
 
-@media (max-width: 1147px) {
+@media (max-width: 1300px) {
 
   .services.main-logo {
     width: 220px;
diff --git a/frontend/src/app/shared/pipes/bitcoinsatoshis.pipe.ts b/frontend/src/app/shared/pipes/bitcoinsatoshis.pipe.ts
index 7065b5138..7e785e9c8 100644
--- a/frontend/src/app/shared/pipes/bitcoinsatoshis.pipe.ts
+++ b/frontend/src/app/shared/pipes/bitcoinsatoshis.pipe.ts
@@ -8,7 +8,7 @@ export class BitcoinsatoshisPipe implements PipeTransform {
 
   constructor(private sanitizer: DomSanitizer) { }
 
-  transform(value: string): SafeHtml {
+  transform(value: string, firstPartClass?: string): SafeHtml {
     const newValue = this.insertSpaces(parseFloat(value || '0').toFixed(8));
     const position = (newValue || '0').search(/[1-9]/);
 
@@ -16,7 +16,7 @@ export class BitcoinsatoshisPipe implements PipeTransform {
     const secondPart = newValue.slice(position);
 
     return this.sanitizer.bypassSecurityTrustHtml(
-      `<span class="text-secondary">${firstPart}</span>${secondPart}`
+      `<span class="${firstPartClass ? firstPartClass : 'text-secondary'}">${firstPart}</span>${secondPart}`
     );
   }
 
diff --git a/frontend/src/app/shared/script.utils.ts b/frontend/src/app/shared/script.utils.ts
index 171112dcc..731e0051b 100644
--- a/frontend/src/app/shared/script.utils.ts
+++ b/frontend/src/app/shared/script.utils.ts
@@ -166,6 +166,7 @@ export const ScriptTemplates: { [type: string]: (...args: any) => ScriptTemplate
   ln_anchor: () => ({ type: 'ln_anchor', label: 'Lightning Anchor' }),
   ln_anchor_swept: () => ({ type: 'ln_anchor_swept', label: 'Swept Lightning Anchor' }),
   multisig: (m: number, n: number) => ({ type: 'multisig', m, n, label: $localize`:@@address-label.multisig:Multisig ${m}:multisigM: of ${n}:multisigN:` }),
+  anchor: () => ({ type: 'anchor', label: 'anchor' }),
 };
 
 export class ScriptInfo {
@@ -266,7 +267,7 @@ export function parseMultisigScript(script: string): undefined | { m: number, n:
   if (!opN) {
     return;
   }
-  if (!opN.startsWith('OP_PUSHNUM_')) {
+  if (opN !== 'OP_0' && !opN.startsWith('OP_PUSHNUM_')) {
     return;
   }
   const n = parseInt(opN.match(/[0-9]+/)?.[0] || '', 10);
@@ -286,7 +287,7 @@ export function parseMultisigScript(script: string): undefined | { m: number, n:
   if (!opM) {
     return;
   }
-  if (!opM.startsWith('OP_PUSHNUM_')) {
+  if (opM !== 'OP_0' && !opM.startsWith('OP_PUSHNUM_')) {
     return;
   }
   const m = parseInt(opM.match(/[0-9]+/)?.[0] || '', 10);
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index 2d5b4d0f9..92b461548 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra
 import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
 import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
   faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
-  faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark} from '@fortawesome/free-solid-svg-icons';
+  faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck } from '@fortawesome/free-solid-svg-icons';
 import { InfiniteScrollModule } from 'ngx-infinite-scroll';
 import { MenuComponent } from '../components/menu/menu.component';
 import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component';
@@ -35,6 +35,7 @@ import { LanguageSelectorComponent } from '../components/language-selector/langu
 import { FiatSelectorComponent } from '../components/fiat-selector/fiat-selector.component';
 import { RateUnitSelectorComponent } from '../components/rate-unit-selector/rate-unit-selector.component';
 import { ThemeSelectorComponent } from '../components/theme-selector/theme-selector.component';
+import { AmountSelectorComponent } from '../components/amount-selector/amount-selector.component';
 import { BrowserOnlyDirective } from './directives/browser-only.directive';
 import { ServerOnlyDirective } from './directives/server-only.directive';
 import { ColoredPriceDirective } from './directives/colored-price.directive';
@@ -131,6 +132,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
     FiatSelectorComponent,
     ThemeSelectorComponent,
     RateUnitSelectorComponent,
+    AmountSelectorComponent,
     ScriptpubkeyTypePipe,
     RelativeUrlPipe,
     NoSanitizePipe,
@@ -278,6 +280,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
     FiatSelectorComponent,
     RateUnitSelectorComponent,
     ThemeSelectorComponent,
+    AmountSelectorComponent,
     ScriptpubkeyTypePipe,
     RelativeUrlPipe,
     Hex2asciiPipe,
@@ -362,6 +365,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
     TwitterWidgetComponent,
     TwitterLogin,
     BitcoinInvoiceComponent,
+    BitcoinsatoshisPipe,
 
     MempoolBlockOverviewComponent,
     ClockchainComponent,
@@ -440,5 +444,6 @@ export class SharedModule {
     library.addIcons(faFaucetDrip);
     library.addIcons(faTimeline);
     library.addIcons(faCircleXmark);
+    library.addIcons(faCalendarCheck);
   }
 }
diff --git a/frontend/src/app/shared/transaction.utils.ts b/frontend/src/app/shared/transaction.utils.ts
index 9d9cd801b..bbf28a250 100644
--- a/frontend/src/app/shared/transaction.utils.ts
+++ b/frontend/src/app/shared/transaction.utils.ts
@@ -1,10 +1,10 @@
 import { TransactionFlags } from './filters.utils';
 import { getVarIntLength, opcodes, parseMultisigScript, isPoint } from './script.utils';
 import { Transaction } from '../interfaces/electrs.interface';
-import { CpfpInfo, RbfInfo } from '../interfaces/node-api.interface';
+import { CpfpInfo, RbfInfo, TransactionStripped } from '../interfaces/node-api.interface';
+import { StateService } from '../services/state.service';
 
 // Bitcoin Core default policy settings
-const TX_MAX_STANDARD_VERSION = 2;
 const MAX_STANDARD_TX_WEIGHT = 400_000;
 const MAX_BLOCK_SIGOPS_COST = 80_000;
 const MAX_STANDARD_TX_SIGOPS_COST = (MAX_BLOCK_SIGOPS_COST / 5);
@@ -89,10 +89,13 @@ export function isDERSig(w: string): boolean {
  *
  * returns true early if any standardness rule is violated, otherwise false
  * (except for non-mandatory-script-verify-flag and p2sh script evaluation rules which are *not* enforced)
+ *
+ * As standardness rules change, we'll need to apply the rules in force *at the time* to older blocks.
+ * For now, just pull out individual rules into versioned functions where necessary.
  */
-export function isNonStandard(tx: Transaction): boolean {
+export function isNonStandard(tx: Transaction, height?: number, network?: string): boolean {
   // version
-  if (tx.version > TX_MAX_STANDARD_VERSION) {
+  if (isNonStandardVersion(tx, height, network)) {
     return true;
   }
 
@@ -139,6 +142,8 @@ export function isNonStandard(tx: Transaction): boolean {
       }
     } else if (['unknown', 'provably_unspendable', 'empty'].includes(vin.prevout?.scriptpubkey_type || '')) {
       return true;
+    } else if (isNonStandardAnchor(tx, height, network)) {
+      return true;
     }
     // TODO: bad-witness-nonstandard
   }
@@ -203,6 +208,51 @@ export function isNonStandard(tx: Transaction): boolean {
   return false;
 }
 
+// Individual versioned standardness rules
+
+const V3_STANDARDNESS_ACTIVATION_HEIGHT = {
+  'testnet4': 42_000,
+  'testnet': 2_900_000,
+  'signet': 211_000,
+  '': 863_500,
+};
+function isNonStandardVersion(tx: Transaction, height?: number, network?: string): boolean {
+  let TX_MAX_STANDARD_VERSION = 3;
+  if (
+    height != null
+    && network != null
+    && V3_STANDARDNESS_ACTIVATION_HEIGHT[network]
+    && height <= V3_STANDARDNESS_ACTIVATION_HEIGHT[network]
+  ) {
+    // V3 transactions were non-standard to spend before v28.x (scheduled for 2024/09/30 https://github.com/bitcoin/bitcoin/issues/29891)
+    TX_MAX_STANDARD_VERSION = 2;
+  }
+
+  if (tx.version > TX_MAX_STANDARD_VERSION) {
+    return true;
+  }
+  return false;
+}
+
+const ANCHOR_STANDARDNESS_ACTIVATION_HEIGHT = {
+  'testnet4': 42_000,
+  'testnet': 2_900_000,
+  'signet': 211_000,
+  '': 863_500,
+};
+function isNonStandardAnchor(tx: Transaction, height?: number, network?: string): boolean {
+  if (
+    height != null
+    && network != null
+    && ANCHOR_STANDARDNESS_ACTIVATION_HEIGHT[network]
+    && height <= ANCHOR_STANDARDNESS_ACTIVATION_HEIGHT[network]
+  ) {
+    // anchor outputs were non-standard to spend before v28.x (scheduled for 2024/09/30 https://github.com/bitcoin/bitcoin/issues/29891)
+    return true;
+  }
+  return false;
+}
+
 // 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
@@ -289,7 +339,7 @@ export function isBurnKey(pubkey: string): boolean {
   ].includes(pubkey);
 }
 
-export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replacement?: boolean): bigint {
+export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replacement?: boolean, height?: number, network?: string): bigint {
   let flags = tx.flags ? BigInt(tx.flags) : 0n;
 
   // Update variable flags (CPFP, RBF)
@@ -439,7 +489,7 @@ export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replac
     flags |= TransactionFlags.batch_payout;
   }
 
-  if (isNonStandard(tx)) {
+  if (isNonStandard(tx, height, network)) {
     flags |= TransactionFlags.nonstandard;
   }
 
@@ -458,4 +508,83 @@ export function getUnacceleratedFeeRate(tx: Transaction, accelerated: boolean):
   } else {
     return tx.effectiveFeePerVsize;
   }
+}
+
+export function identifyPrioritizedTransactions(transactions: TransactionStripped[]): { prioritized: string[], deprioritized: string[] } {
+  // find the longest increasing subsequence of transactions
+  // (adapted from https://en.wikipedia.org/wiki/Longest_increasing_subsequence#Efficient_algorithms)
+  // should be O(n log n)
+  const X = transactions.slice(1).reverse(); // standard block order is by *decreasing* effective fee rate, but we want to iterate in increasing order (and skip the coinbase)
+  if (X.length < 2) {
+    return { prioritized: [], deprioritized: [] };
+  }
+  const N = X.length;
+  const P: number[] = new Array(N);
+  const M: number[] = new Array(N + 1);
+  M[0] = -1; // undefined so can be set to any value
+
+  let L = 0;
+  for (let i = 0; i < N; i++) {
+    // Binary search for the smallest positive l ≤ L
+    // such that X[M[l]].effectiveFeePerVsize > X[i].effectiveFeePerVsize
+    let lo = 1;
+    let hi = L + 1;
+    while (lo < hi) {
+      const mid = lo + Math.floor((hi - lo) / 2); // lo <= mid < hi
+      if (X[M[mid]].rate > X[i].rate) {
+        hi = mid;
+      } else { // if X[M[mid]].effectiveFeePerVsize < X[i].effectiveFeePerVsize
+        lo = mid + 1;
+      }
+    }
+
+    // After searching, lo == hi is 1 greater than the
+    // length of the longest prefix of X[i]
+    const newL = lo;
+
+    // The predecessor of X[i] is the last index of
+    // the subsequence of length newL-1
+    P[i] = M[newL - 1];
+    M[newL] = i;
+
+    if (newL > L) {
+      // If we found a subsequence longer than any we've
+      // found yet, update L
+      L = newL;
+    }
+  }
+
+  // Reconstruct the longest increasing subsequence
+  // It consists of the values of X at the L indices:
+  // ..., P[P[M[L]]], P[M[L]], M[L]
+  const LIS: TransactionStripped[] = new Array(L);
+  let k = M[L];
+  for (let j = L - 1; j >= 0; j--) {
+    LIS[j] = X[k];
+    k = P[k];
+  }
+
+  const lisMap = new Map<string, number>();
+  LIS.forEach((tx, index) => lisMap.set(tx.txid, index));
+
+  const prioritized: string[] = [];
+  const deprioritized: string[] = [];
+
+  let lastRate = 0;
+
+  for (const tx of X) {
+    if (lisMap.has(tx.txid)) {
+      lastRate = tx.rate;
+    } else {
+      if (Math.abs(tx.rate - lastRate) < 0.1) {
+        // skip if the rate is almost the same as the previous transaction
+      } else if (tx.rate <= lastRate) {
+        prioritized.push(tx.txid);
+      } else {
+        deprioritized.push(tx.txid);
+      }
+    }
+  }
+
+  return { prioritized, deprioritized };
 }
\ No newline at end of file
diff --git a/frontend/src/locale/messages.hr.xlf b/frontend/src/locale/messages.hr.xlf
index e0f2ceb89..d013c70bd 100644
--- a/frontend/src/locale/messages.hr.xlf
+++ b/frontend/src/locale/messages.hr.xlf
@@ -1567,7 +1567,7 @@
       </trans-unit>
       <trans-unit id="bdb8bbb38e4ca3c73e19dc4167fbe4aec316f818" datatype="html">
         <source>Total Bid Boost</source>
-        <target>Ukupno povećanje ponude</target>
+        <target>Total Bid Boost</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html</context>
           <context context-type="linenumber">11</context>
@@ -1969,7 +1969,7 @@
       </trans-unit>
       <trans-unit id="841f2a74ae5095e6e37f5749f3cc1851cf36a420" datatype="html">
         <source>Avg Max Bid</source>
-        <target>Prosječna maks. ponuda</target>
+        <target>Prosj. max ponuda</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/acceleration/pending-stats/pending-stats.component.html</context>
           <context context-type="linenumber">11</context>
@@ -3622,7 +3622,7 @@
       </trans-unit>
       <trans-unit id="14779b0ce4cbc4d975a35a8fe074426228a324f3" datatype="html">
         <source><x id="INTERPOLATION" equiv-text="transactions&lt;/ng-template&gt;   &lt;/h2&gt;   &lt;ngb-pagination class=&quot;pagination-container float-ri"/> transactions</source>
-        <target><x id="INTERPOLATION" equiv-text="transactions&lt;/ng-template&gt;   &lt;/h2&gt;   &lt;ngb-pagination class=&quot;pagination-container float-ri"/> transakcije</target>
+        <target><x id="INTERPOLATION" equiv-text="transactions&lt;/ng-template&gt;   &lt;/h2&gt;   &lt;ngb-pagination class=&quot;pagination-container float-ri"/> transakcija</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/block/block-transactions.component.html</context>
           <context context-type="linenumber">5</context>
@@ -4046,7 +4046,7 @@
       </trans-unit>
       <trans-unit id="8a7b4bd44c0ac71b2e72de0398b303257f7d2f54" datatype="html">
         <source>Blocks</source>
-        <target>Blokovi</target>
+        <target>Blokova</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/blocks-list/blocks-list.component.html</context>
           <context context-type="linenumber">4</context>
@@ -4360,7 +4360,7 @@
       </trans-unit>
       <trans-unit id="23c872b0336e20284724607f2887da39bd8142c3" datatype="html">
         <source>Previous fee</source>
-        <target>Prethodna naknada</target>
+        <target>Preth. naknada</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/custom-dashboard/custom-dashboard.component.html</context>
           <context context-type="linenumber">107</context>
@@ -4594,7 +4594,7 @@
       </trans-unit>
       <trans-unit id="1bb6965f8e1bbe40c076528ffd841da86f57f119" datatype="html">
         <source><x id="INTERPOLATION" equiv-text="&lt;span class=&quot;shared-block&quot;&gt;blocks&lt;/span&gt;&lt;/ng-template&gt;             &lt;ng-"/> <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;shared-block&quot;&gt;"/>blocks<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></source>
-        <target><x id="INTERPOLATION" equiv-text="&lt;span class=&quot;shared-block&quot;&gt;blocks&lt;/span&gt;&lt;/ng-template&gt;             &lt;ng-"/> <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;shared-block&quot;&gt;"/>blokovi<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
+        <target><x id="INTERPOLATION" equiv-text="&lt;span class=&quot;shared-block&quot;&gt;blocks&lt;/span&gt;&lt;/ng-template&gt;             &lt;ng-"/> <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;shared-block&quot;&gt;"/>blokova<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/difficulty-mining/difficulty-mining.component.html</context>
           <context context-type="linenumber">10,11</context>
@@ -4671,7 +4671,7 @@
       </trans-unit>
       <trans-unit id="df71fa93f0503396ea2bb3ba5161323330314d6c" datatype="html">
         <source>Next Halving</source>
-        <target>Sljedeće prepolovljenje</target>
+        <target>Slj prepolovljenje</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/difficulty-mining/difficulty-mining.component.html</context>
           <context context-type="linenumber">47</context>
@@ -5465,7 +5465,7 @@
       </trans-unit>
       <trans-unit id="1a8246eba9a999ee881248c4767d63b875ef07fe" datatype="html">
         <source>blocks</source>
-        <target>blokovi</target>
+        <target>blokova</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component.html</context>
           <context context-type="linenumber">63</context>
@@ -5885,7 +5885,7 @@
       </trans-unit>
       <trans-unit id="9ef8b357c32266f8423e24bf654006d3aa8fcd0b" datatype="html">
         <source>Blocks (1w)</source>
-        <target>Blokova (1w)</target>
+        <target>Blokova (1 tj)</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.html</context>
           <context context-type="linenumber">25</context>
@@ -6165,7 +6165,7 @@
       </trans-unit>
       <trans-unit id="3dc78651b2810cbb6e830fe7e57499d8cf6a8e4d" datatype="html">
         <source>Blocks (24h)</source>
-        <target>Blokovi (24h)</target>
+        <target>Blokova (24h)</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/pool/pool.component.html</context>
           <context context-type="linenumber">120</context>
@@ -6815,7 +6815,7 @@
       </trans-unit>
       <trans-unit id="time-until" datatype="html">
         <source>In ~<x id="DATE" equiv-text="dateStrings.i18nYear"/></source>
-        <target>U ~<x id="DATE" equiv-text="dateStrings.i18nYear"/></target>
+        <target>Za ~<x id="DATE" equiv-text="dateStrings.i18nYear"/></target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/time/time.component.ts</context>
           <context context-type="linenumber">188</context>
diff --git a/frontend/src/locale/messages.nb.xlf b/frontend/src/locale/messages.nb.xlf
index 5fbc688c4..b2de6219c 100644
--- a/frontend/src/locale/messages.nb.xlf
+++ b/frontend/src/locale/messages.nb.xlf
@@ -510,7 +510,7 @@
       </trans-unit>
       <trans-unit id="e4b2d9e6a2ab9e6ca34027ec03beaac42b7badd4" datatype="html">
         <source>sats</source>
-        <target>sats</target>
+        <target>sat</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/accelerate-checkout/accelerate-checkout.component.html</context>
           <context context-type="linenumber">57</context>
@@ -881,7 +881,7 @@
       </trans-unit>
       <trans-unit id="65fd4251d8ddfe4017d4d83f8cec6f5a80d89289" datatype="html">
         <source>Pay</source>
-        <target>Betale</target>
+        <target>Betal</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/accelerate-checkout/accelerate-checkout.component.html</context>
           <context context-type="linenumber">378</context>
@@ -4846,7 +4846,7 @@
       </trans-unit>
       <trans-unit id="615ba6c4511a36f93c225c725935fdbf16f162a5" datatype="html">
         <source>Amount (sats)</source>
-        <target>Beløp (sats)</target>
+        <target>Beløp (sat)</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/faucet/faucet.component.html</context>
           <context context-type="linenumber">51</context>
@@ -6442,7 +6442,7 @@
       </trans-unit>
       <trans-unit id="31443c29cb161e8aa661eb5035f675746ef95b45" datatype="html">
         <source>sats/tx</source>
-        <target>sats/tx</target>
+        <target>sat/tx</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/reward-stats/reward-stats.component.html</context>
           <context context-type="linenumber">33</context>
@@ -8145,7 +8145,7 @@
       </trans-unit>
       <trans-unit id="6acd06bd5a3af583cd46c6d9f7954d7a2b44095e" datatype="html">
         <source>mSats</source>
-        <target>mSats</target>
+        <target>mSat</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
           <context context-type="linenumber">35</context>
diff --git a/frontend/src/resources/mempool-block-visualization.png b/frontend/src/resources/mempool-block-visualization.png
new file mode 100644
index 000000000..7b808a69a
Binary files /dev/null and b/frontend/src/resources/mempool-block-visualization.png differ
diff --git a/frontend/src/resources/mempool-research.png b/frontend/src/resources/mempool-research.png
new file mode 100644
index 000000000..42ee008f7
Binary files /dev/null and b/frontend/src/resources/mempool-research.png differ
diff --git a/frontend/src/resources/mempool-transaction.png b/frontend/src/resources/mempool-transaction.png
new file mode 100644
index 000000000..396f47633
Binary files /dev/null and b/frontend/src/resources/mempool-transaction.png differ
diff --git a/frontend/src/resources/profile/bitkey.svg b/frontend/src/resources/profile/bitkey.svg
new file mode 100644
index 000000000..875436402
--- /dev/null
+++ b/frontend/src/resources/profile/bitkey.svg
@@ -0,0 +1,3 @@
+<svg width="212" height="450" viewBox="0 0 212 450" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M114.005 2.14659C109.052 -0.71553 102.948 -0.715531 97.9948 2.14659L7.99481 54.1531C3.04732 57.012 0 62.2924 0 68.0065V172.043C0 177.758 3.04733 183.038 7.99482 185.897L69.0247 221.163C73.9722 224.022 77.0195 229.302 77.0195 235.016V433.998V437.998C77.0195 444.625 82.3921 449.998 89.0195 449.998H93.0195H118.981H123.031C129.658 449.998 135.031 444.625 135.031 437.998V233.751C135.444 228.531 138.395 223.809 142.975 221.163L204.005 185.897C208.953 183.038 212 177.758 212 172.043V68.0065C212 62.2924 208.953 57.012 204.005 54.1531L114.005 2.14659ZM112.07 68.1162C108.334 65.938 103.716 65.938 99.9803 68.1162L63.9718 89.1129C60.2841 91.2631 58.0164 95.2105 58.0164 99.4793V141.68C58.0164 145.948 60.2841 149.896 63.9718 152.046L99.9803 173.043C103.716 175.221 108.334 175.221 112.07 173.043L148.078 152.046C151.766 149.896 154.034 145.948 154.034 141.68V99.4793C154.034 95.2105 151.766 91.2631 148.078 89.1129L112.07 68.1162ZM172.046 338.527C164.047 333.861 154 339.631 154 348.892L154 356L154 412V419.108C154 428.369 164.047 434.14 172.046 429.473L196.046 415.473C199.733 413.322 202 409.376 202 405.108V362.892C202 358.624 199.733 354.678 196.046 352.527L172.046 338.527Z" fill="white"/>
+</svg>
diff --git a/frontend/src/resources/profile/leather.svg b/frontend/src/resources/profile/leather.svg
new file mode 100644
index 000000000..a909606fa
--- /dev/null
+++ b/frontend/src/resources/profile/leather.svg
@@ -0,0 +1,3 @@
+<svg width="387" height="377" viewBox="-58.05 -56.55 503.1 490.1" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M249.327 137.966C283.745 132.618 333.52 96.2553 333.52 67.9135C333.52 59.3574 326.637 53.4752 316.576 53.4752C297.513 53.4752 265.212 82.3518 249.327 137.966ZM89.9409 278.606C44.9317 278.606 41.225 323.525 86.2343 323.525C106.356 323.525 130.714 315.504 143.422 301.065C124.889 285.023 109.533 278.606 89.9409 278.606ZM376.411 259.355C379.059 334.755 340.934 377 276.332 377C238.207 377 219.144 362.562 178.371 335.824C157.19 359.353 116.946 377 83.5867 377C-31.3192 377 -26.5536 229.943 90.4704 229.943C114.828 229.943 135.48 236.36 161.956 252.938L179.43 191.441C107.415 171.655 71.4077 116.041 106.886 36.3631H164.074C132.303 89.3035 154.013 133.153 194.256 137.966C215.967 60.4269 262.565 0 324.518 0C359.467 0 387.002 22.9943 387.002 64.705C387.002 131.549 300.161 186.094 234.5 191.441L207.494 287.162C238.207 322.99 323.459 357.749 323.459 259.355H376.411Z" fill="#F5F1ED"/>
+</svg>
\ No newline at end of file
diff --git a/production/README.md b/production/README.md
index 3f1b24d22..2805cde81 100644
--- a/production/README.md
+++ b/production/README.md
@@ -84,11 +84,11 @@ pkg install -y zsh sudo git screen curl wget neovim rsync nginx openssl openssh-
 
 ### Node.js + npm
 
-Build Node.js v16.16.0 and npm v8 from source using `nvm`:
+Build Node.js v20.17.0 and npm v9 from source using `nvm`:
 ```
-curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | zsh
 source $HOME/.zshrc
-nvm install v16.16.0 --shared-zlib
+nvm install v20.17.0 --shared-zlib
 nvm alias default node
 ```
 
diff --git a/production/bitcoin.conf b/production/bitcoin.conf
index 1b4eb1171..63baa32b5 100644
--- a/production/bitcoin.conf
+++ b/production/bitcoin.conf
@@ -15,6 +15,7 @@ rpcpassword=__BITCOIN_RPC_PASS__
 whitelist=127.0.0.1
 whitelist=103.99.168.0/22
 whitelist=2401:b140::/32
+blocksxor=0
 #uacomment=@wiz
 
 [main]
diff --git a/production/install b/production/install
index 30754863c..bf7153557 100755
--- a/production/install
+++ b/production/install
@@ -392,9 +392,9 @@ DEBIAN_UNFURL_PKG+=(libxdamage-dev libxrandr-dev libgbm-dev libpango1.0-dev liba
 # packages needed for mempool ecosystem
 FREEBSD_PKG=()
 FREEBSD_PKG+=(zsh sudo git git-lfs screen curl wget calc neovim)
-FREEBSD_PKG+=(openssh-portable py39-pip rust llvm10 jq base64 libzmq4)
+FREEBSD_PKG+=(openssh-portable py311-pip rust llvm18 jq base64 libzmq4)
 FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
-FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb1011-server keybase)
+FREEBSD_PKG+=(nginx rsync py311-certbot-nginx mariadb1011-server)
 FREEBSD_PKG+=(geoipupdate redis)
 
 FREEBSD_UNFURL_PKG=()
diff --git a/production/mempool-build-all b/production/mempool-build-all
index 601f15b9a..84ea1b5ec 100755
--- a/production/mempool-build-all
+++ b/production/mempool-build-all
@@ -40,7 +40,7 @@ update_repo()
     git fetch origin || exit 1
     for remote in origin;do
         git remote add "${remote}" "https://github.com/${remote}/mempool" >/dev/null 2>&1
-        git fetch "${remote}" || exit 1
+        git fetch "${remote}" --tags || exit 1
     done
 
     if [ $(git tag -l "${REF}") ];then
diff --git a/production/mempool-reset-all b/production/mempool-reset-all
index 22f004610..d7e8ba249 100755
--- a/production/mempool-reset-all
+++ b/production/mempool-reset-all
@@ -1,3 +1,5 @@
 #!/usr/bin/env zsh
-rm $HOME/*/backend/mempool-config.json
-rm $HOME/*/frontend/mempool-frontend-config.json
+rm -f $HOME/*/backend/mempool-config.json
+rm -f $HOME/*/frontend/mempool-frontend-config.json
+rm -f $HOME/*/frontend/projects/mempool/mempool-frontend-config.json
+exit 0
diff --git a/production/nginx/location-api-v1-services.conf b/production/nginx/location-api-v1-services.conf
index 88f510e79..a9df64bc6 100644
--- a/production/nginx/location-api-v1-services.conf
+++ b/production/nginx/location-api-v1-services.conf
@@ -92,6 +92,7 @@ location @mempool-api-v1-services-cache-disabled-addcors {
 	set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
 	set $cors_origin 'https://mempool.space';
 	set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
+	set $cors_expose_headers 'X-Total-Count';
 	set $cors_credentials 'true';
 
 	# set CORS for approved hostnames
@@ -100,6 +101,7 @@ location @mempool-api-v1-services-cache-disabled-addcors {
 		set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
 		set $cors_origin "$http_origin";
 		set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
+		set $cors_expose_headers 'X-Total-Count';
 		set $cors_credentials 'true';
 	}
 
@@ -108,6 +110,7 @@ location @mempool-api-v1-services-cache-disabled-addcors {
 	add_header Access-Control-Allow-Origin "$cors_origin" always;
 	add_header Access-Control-Allow-Headers "$cors_headers" always;
 	add_header Access-Control-Allow-Credentials "$cors_credentials" always;
+	add_header Access-Control-Expose-Headers "$cors_expose_headers" always;
 
 	proxy_redirect off;
 	proxy_buffering off;
@@ -172,6 +175,7 @@ location @mempool-api-v1-services-cache-short-addcors {
 	set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
 	set $cors_origin 'https://mempool.space';
 	set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
+	set $cors_expose_headers 'X-Total-Count';
 	set $cors_credentials 'true';
 
 	# set CORS for approved hostnames
@@ -180,6 +184,7 @@ location @mempool-api-v1-services-cache-short-addcors {
 		set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
 		set $cors_origin "$http_origin";
 		set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
+		set $cors_expose_headers 'X-Total-Count';
 		set $cors_credentials 'true';
 	}
 
@@ -188,6 +193,7 @@ location @mempool-api-v1-services-cache-short-addcors {
 	add_header Access-Control-Allow-Origin "$cors_origin" always;
 	add_header Access-Control-Allow-Headers "$cors_headers" always;
 	add_header Access-Control-Allow-Credentials "$cors_credentials" always;
+	add_header Access-Control-Expose-Headers "$cors_expose_headers" always;
 
 	# add our own cache headers
 	add_header 'Pragma' 'public';
diff --git a/unfurler/package-lock.json b/unfurler/package-lock.json
index 92b9d307b..799148486 100644
--- a/unfurler/package-lock.json
+++ b/unfurler/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "mempool-unfurl",
-  "version": "3.0.0-beta",
+  "version": "3.1.0-dev",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "mempool-unfurl",
-      "version": "3.0.0-beta",
+      "version": "3.0.0",
       "dependencies": {
         "@types/node": "^16.11.41",
         "ejs": "^3.1.10",
diff --git a/unfurler/package.json b/unfurler/package.json
index 8a87e88d8..bf3dad55b 100644
--- a/unfurler/package.json
+++ b/unfurler/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mempool-unfurl",
-  "version": "3.0.0-beta",
+  "version": "3.1.0-dev",
   "description": "Renderer for mempool open graph link preview images",
   "repository": {
     "type": "git",