Compare commits
213 Commits
mononaut/p
...
v3.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79fb0a34dc | ||
|
|
40f79e64b3 | ||
|
|
5f034d34b3 | ||
|
|
6d336c010c | ||
|
|
14c87e2f03 | ||
|
|
6ef15bddcd | ||
|
|
fbc6c078ef | ||
|
|
717601342f | ||
|
|
7145856d98 | ||
|
|
f305dba50c | ||
|
|
5523c77a04 | ||
|
|
d7c60d9b11 | ||
|
|
4ca273c8c1 | ||
|
|
59a92d0363 | ||
|
|
9ca41a7608 | ||
|
|
1bc02fd216 | ||
|
|
413941f5ac | ||
|
|
68854d56cc | ||
|
|
a78376a1c5 | ||
|
|
a95f1e583d | ||
|
|
0783507821 | ||
|
|
76621464f1 | ||
|
|
21ede8671d | ||
|
|
e27cf33981 | ||
|
|
02c892aa07 | ||
|
|
5302b27d55 | ||
|
|
5b357eb6b5 | ||
|
|
10be2c8176 | ||
|
|
e25e99456c | ||
|
|
e6236d8cfd | ||
|
|
75c50416f5 | ||
|
|
0808640ec0 | ||
|
|
7dad00523f | ||
|
|
63a4810b11 | ||
|
|
0e69f28e58 | ||
|
|
77f8999f1e | ||
|
|
0e4e0bff78 | ||
|
|
504840c15a | ||
|
|
b7919afd2f | ||
|
|
7c09dd1b70 | ||
|
|
679e967241 | ||
|
|
f21a31ce86 | ||
|
|
003f3cf8b1 | ||
|
|
3ec8a6955c | ||
|
|
fba4759ba1 | ||
|
|
ea2f39b18a | ||
|
|
ad77b3a6c9 | ||
|
|
097db4edcd | ||
|
|
e9e2c2f7b5 | ||
|
|
7a1cd0ff6a | ||
|
|
f83fed62ce | ||
|
|
72fdcb749f | ||
|
|
21a78e37d1 | ||
|
|
feed25b641 | ||
|
|
5e95d8d6ce | ||
|
|
6de8cbe9b9 | ||
|
|
9cf2db77b4 | ||
|
|
372fb38722 | ||
|
|
c35f8a3f08 | ||
|
|
4ed629a8ea | ||
|
|
67b2aa2ff0 | ||
|
|
36dc5e9a9b | ||
|
|
928d3282a2 | ||
|
|
a567f44b3c | ||
|
|
363b9f5be2 | ||
|
|
52725df296 | ||
|
|
e3cd7dbf34 | ||
|
|
593e72f905 | ||
|
|
8c2d72ad58 | ||
|
|
d0696628b2 | ||
|
|
368ab1dc66 | ||
|
|
b6f66a6a28 | ||
|
|
bda361cf57 | ||
|
|
1a6a0c12ec | ||
|
|
b645ad3fd8 | ||
|
|
b979594a04 | ||
|
|
6aceb898df | ||
|
|
c80201e3db | ||
|
|
ef5d2606b7 | ||
|
|
1e648a2dce | ||
|
|
11058b469c | ||
|
|
e79c32010d | ||
|
|
5e4742e51b | ||
|
|
38d30d23e5 | ||
|
|
47dd2cbd5d | ||
|
|
6b8db33a9c | ||
|
|
9b17c16325 | ||
|
|
83c285e17d | ||
|
|
5fabf892ba | ||
|
|
0ba4e98f52 | ||
|
|
a1b75c3772 | ||
|
|
0712195de4 | ||
|
|
4d6c5a7ce7 | ||
|
|
71d4aa9ddd | ||
|
|
827b0f6ad1 | ||
|
|
e8d85613b4 | ||
|
|
ce3b599bab | ||
|
|
47a7564cfc | ||
|
|
7744146ef7 | ||
|
|
9cb72cff31 | ||
|
|
98119b3e54 | ||
|
|
f5a3e78cbe | ||
|
|
515d2656e7 | ||
|
|
339a21caaa | ||
|
|
a7c0d33de8 | ||
|
|
d1ef1afc9c | ||
|
|
bf4a1fcd7a | ||
|
|
d6044331e1 | ||
|
|
9fdb164b64 | ||
|
|
d691bf2714 | ||
|
|
cd366177ba | ||
|
|
6312884234 | ||
|
|
d1f26f0491 | ||
|
|
ed12e30517 | ||
|
|
cb363aca23 | ||
|
|
c753a8e92a | ||
|
|
c44276027c | ||
|
|
91fbd0864b | ||
|
|
c2c4047ffd | ||
|
|
9427ba96a2 | ||
|
|
7c11f0a3da | ||
|
|
6780ba82d0 | ||
|
|
6d643604d0 | ||
|
|
7ae4b451e4 | ||
|
|
bbd1f088d1 | ||
|
|
3060aecddb | ||
|
|
7a765cecd9 | ||
|
|
03e9592c79 | ||
|
|
c7b89f31dd | ||
|
|
899b230760 | ||
|
|
d787ef99c4 | ||
|
|
043ab008db | ||
|
|
4abc4e96a8 | ||
|
|
7fd8790750 | ||
|
|
8ba4a7b421 | ||
|
|
78ea9cbd16 | ||
|
|
727937b8ae | ||
|
|
5f4add3e22 | ||
|
|
c4f8afbaf7 | ||
|
|
528877f43f | ||
|
|
a3d61fa525 | ||
|
|
c0308fbc0d | ||
|
|
24696d0408 | ||
|
|
b9838fda8d | ||
|
|
726bd51abb | ||
|
|
1fe08d1234 | ||
|
|
1ca136fa75 | ||
|
|
fcecbe4967 | ||
|
|
d42a3f74ec | ||
|
|
dc44f1b618 | ||
|
|
0fde6dd908 | ||
|
|
4eb494baec | ||
|
|
c1b2f1f2c7 | ||
|
|
a6d2887847 | ||
|
|
f9895a492c | ||
|
|
ac56f70f6f | ||
|
|
f295392dca | ||
|
|
c89e283c8e | ||
|
|
3aa938a94b | ||
|
|
20fff97804 | ||
|
|
d1f7026804 | ||
|
|
2da31c4d4a | ||
|
|
975ba653a2 | ||
|
|
c8100712e8 | ||
|
|
a7b2c39f51 | ||
|
|
3d9fa5076b | ||
|
|
9fa715e403 | ||
|
|
9ee11b64e9 | ||
|
|
8c90d4ba98 | ||
|
|
b333c211f7 | ||
|
|
8fb566858f | ||
|
|
379e1470fd | ||
|
|
c5f0608b46 | ||
|
|
7544357826 | ||
|
|
5005817529 | ||
|
|
bfc3fbe397 | ||
|
|
30be7e0f7a | ||
|
|
18eb6600b0 | ||
|
|
88c0d04c18 | ||
|
|
8ea7bb907c | ||
|
|
b595002c25 | ||
|
|
7674bee5ad | ||
|
|
0ee28a335f | ||
|
|
285485c69e | ||
|
|
b8222bff43 | ||
|
|
3321ae0f1f | ||
|
|
4010047344 | ||
|
|
ce290a449c | ||
|
|
b9c151f549 | ||
|
|
2ff3e4acb9 | ||
|
|
a1e2d2fd74 | ||
|
|
e82b43e340 | ||
|
|
22886cb32d | ||
|
|
ef554ad67b | ||
|
|
b64b6fb3c7 | ||
|
|
2819cea509 | ||
|
|
2a8a403da7 | ||
|
|
e4fcadf39b | ||
|
|
c4f2f4ca66 | ||
|
|
0c1221dc07 | ||
|
|
d1c9e8b56e | ||
|
|
1b2122cd35 | ||
|
|
5aff2c74e6 | ||
|
|
1bdbb1b908 | ||
|
|
91e3943a74 | ||
|
|
23b871631a | ||
|
|
ab911d5c9e | ||
|
|
45ba50e82c | ||
|
|
fc73dcc344 | ||
|
|
683d775c7e | ||
|
|
48e3be875d | ||
|
|
597073c9b6 | ||
|
|
d7ac326f92 |
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["16", "17", "18", "20"]
|
||||
node: ["18", "20"]
|
||||
flavor: ["dev", "prod"]
|
||||
fail-fast: false
|
||||
runs-on: "ubuntu-latest"
|
||||
@@ -27,8 +27,17 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install 1.63.x Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@1.63
|
||||
- name: Read rust-toolchain file from repository
|
||||
id: gettoolchain
|
||||
run: echo "::set-output name=toolchain::$(cat rust-toolchain)"
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}
|
||||
|
||||
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
|
||||
# Latest version available on this commit is 1.71.1
|
||||
# Commit date is Aug 3, 2023
|
||||
uses: dtolnay/rust-toolchain@f361669954a8ecfc00a3443f35f9ac8e610ffc06
|
||||
with:
|
||||
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
|
||||
|
||||
- name: Install
|
||||
if: ${{ matrix.flavor == 'dev'}}
|
||||
@@ -58,7 +67,7 @@ jobs:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["16", "17", "18", "20"]
|
||||
node: ["18", "20"]
|
||||
flavor: ["dev", "prod"]
|
||||
fail-fast: false
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
2
.github/workflows/cypress.yml
vendored
2
.github/workflows/cypress.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||
|
||||
|
||||
19
.github/workflows/get_backend_hash.yml
vendored
Normal file
19
.github/workflows/get_backend_hash.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: 'Print backend hashes'
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
print-backend-sha:
|
||||
runs-on: 'ubuntu-latest'
|
||||
name: Print backend hashes
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: repo
|
||||
|
||||
- name: Run script
|
||||
working-directory: repo
|
||||
run: |
|
||||
chmod +x ./scripts/get_backend_hash.sh
|
||||
sh ./scripts/get_backend_hash.sh
|
||||
8
.github/workflows/on-tag.yml
vendored
8
.github/workflows/on-tag.yml
vendored
@@ -68,17 +68,17 @@ jobs:
|
||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Init repo for Dockerization
|
||||
run: docker/init.sh "$TAG"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
id: qemu
|
||||
|
||||
- name: Setup Docker buildx action
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
id: buildx
|
||||
|
||||
- name: Available platforms
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
docker buildx build \
|
||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
||||
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
|
||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
|
||||
--output "type=registry" ./${{ matrix.service }}/ \
|
||||
|
||||
47
GNUmakefile
47
GNUmakefile
@@ -1,47 +0,0 @@
|
||||
# If you see pwd_unknown showing up check permissions
|
||||
PWD ?= pwd_unknown
|
||||
|
||||
# DATABASE DEPLOY FOLDER CONFIG - default ./data
|
||||
ifeq ($(data),)
|
||||
DATA := data
|
||||
export DATA
|
||||
else
|
||||
DATA := $(data)
|
||||
export DATA
|
||||
endif
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo ''
|
||||
@echo ''
|
||||
@echo ' Usage: make [COMMAND]'
|
||||
@echo ''
|
||||
@echo ' make all # build init mempool and electrs'
|
||||
@echo ' make init # setup some useful configs'
|
||||
@echo ' make mempool # build q dockerized mempool.space'
|
||||
@echo ' make electrs # build a docker electrs image'
|
||||
@echo ''
|
||||
|
||||
.PHONY: init
|
||||
init:
|
||||
@echo ''
|
||||
mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/data
|
||||
#REF: https://github.com/mempool/mempool/blob/master/docker/README.md
|
||||
cat docker/docker-compose.yml > docker-compose.yml
|
||||
cat backend/mempool-config.sample.json > backend/mempool-config.json
|
||||
.PHONY: mempool
|
||||
mempool: init
|
||||
@echo ''
|
||||
docker-compose up --force-recreate --always-recreate-deps
|
||||
@echo ''
|
||||
.PHONY: electrs
|
||||
electrum:
|
||||
#REF: https://hub.docker.com/r/beli/electrum
|
||||
@echo ''
|
||||
docker build -f docker/electrum/Dockerfile .
|
||||
@echo ''
|
||||
.PHONY: all
|
||||
all: init
|
||||
make mempool
|
||||
#######################
|
||||
-include Makefile
|
||||
@@ -85,7 +85,7 @@ Install dependencies with `npm` and build the backend:
|
||||
|
||||
```
|
||||
cd backend
|
||||
npm install
|
||||
npm install --no-install-links # npm@9.4.2 and later can omit the --no-install-links
|
||||
npm run build
|
||||
```
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@
|
||||
"DATABASE": "mempool",
|
||||
"USERNAME": "mempool",
|
||||
"PASSWORD": "mempool",
|
||||
"TIMEOUT": 180000
|
||||
"TIMEOUT": 180000,
|
||||
"PID_DIR": ""
|
||||
},
|
||||
"SYSLOG": {
|
||||
"ENABLED": true,
|
||||
|
||||
586
backend/package-lock.json
generated
586
backend/package-lock.json
generated
@@ -9,12 +9,12 @@
|
||||
"version": "3.0.0-dev",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^18.15.3",
|
||||
"axios": "~1.4.0",
|
||||
"axios": "~1.5.0",
|
||||
"bitcoinjs-lib": "~6.1.3",
|
||||
"crypto-js": "~4.1.1",
|
||||
"crypto-js": "~4.2.0",
|
||||
"express": "~4.18.2",
|
||||
"maxmind": "~4.3.11",
|
||||
"mysql2": "~3.6.0",
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/express": "^4.17.17",
|
||||
@@ -65,47 +65,48 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
|
||||
"integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.18.6"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz",
|
||||
"integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz",
|
||||
"integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz",
|
||||
"integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
|
||||
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-compilation-targets": "^7.21.4",
|
||||
"@babel/helper-module-transforms": "^7.21.2",
|
||||
"@babel/helpers": "^7.21.0",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-compilation-targets": "^7.22.15",
|
||||
"@babel/helper-module-transforms": "^7.23.0",
|
||||
"@babel/helpers": "^7.23.2",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.2",
|
||||
"semver": "^6.3.0"
|
||||
"json5": "^2.2.3",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -115,13 +116,19 @@
|
||||
"url": "https://opencollective.com/babel"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz",
|
||||
"integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
@@ -145,87 +152,84 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz",
|
||||
"integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
|
||||
"integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.21.4",
|
||||
"@babel/helper-validator-option": "^7.21.0",
|
||||
"browserslist": "^4.21.3",
|
||||
"@babel/compat-data": "^7.22.9",
|
||||
"@babel/helper-validator-option": "^7.22.15",
|
||||
"browserslist": "^4.21.9",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.0"
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
|
||||
"integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
|
||||
"integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
|
||||
"integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.4"
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
|
||||
"integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
|
||||
"integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.2",
|
||||
"@babel/types": "^7.21.2"
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-module-imports": "^7.22.15",
|
||||
"@babel/helper-simple-access": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/helper-validator-identifier": "^7.22.20"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
@@ -238,78 +242,78 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
|
||||
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.20.2"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
|
||||
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz",
|
||||
"integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
|
||||
"integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz",
|
||||
"integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
|
||||
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.0",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -317,9 +321,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz",
|
||||
"integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -506,33 +510,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
|
||||
"integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/parser": "^7.20.7",
|
||||
"@babel/types": "^7.20.7"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz",
|
||||
"integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.21.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -541,13 +545,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz",
|
||||
"integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2321,9 +2325,9 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
|
||||
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
|
||||
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -2590,9 +2594,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.21.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
|
||||
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
|
||||
"version": "4.22.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
||||
"integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2602,13 +2606,17 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001449",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"node-releases": "^2.0.8",
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
"caniuse-lite": "^1.0.30001541",
|
||||
"electron-to-chromium": "^1.4.535",
|
||||
"node-releases": "^2.0.13",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -2700,9 +2708,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001473",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
|
||||
"integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==",
|
||||
"version": "1.0.30001547",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz",
|
||||
"integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2892,9 +2900,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
|
||||
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
@@ -3023,9 +3031,9 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.348",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz",
|
||||
"integrity": "sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ==",
|
||||
"version": "1.4.551",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
|
||||
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
@@ -6184,9 +6192,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
|
||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
@@ -7399,9 +7407,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -7411,6 +7419,10 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
@@ -7418,7 +7430,7 @@
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist-lint": "cli.js"
|
||||
"update-browserslist-db": "cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"browserslist": ">= 4.21.0"
|
||||
@@ -7683,50 +7695,59 @@
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
|
||||
"integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.18.6"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz",
|
||||
"integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz",
|
||||
"integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz",
|
||||
"integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
|
||||
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-compilation-targets": "^7.21.4",
|
||||
"@babel/helper-module-transforms": "^7.21.2",
|
||||
"@babel/helpers": "^7.21.0",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-compilation-targets": "^7.22.15",
|
||||
"@babel/helper-module-transforms": "^7.23.0",
|
||||
"@babel/helpers": "^7.23.2",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.2",
|
||||
"semver": "^6.3.0"
|
||||
"json5": "^2.2.3",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz",
|
||||
"integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
@@ -7746,66 +7767,63 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-compilation-targets": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz",
|
||||
"integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
|
||||
"integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.21.4",
|
||||
"@babel/helper-validator-option": "^7.21.0",
|
||||
"browserslist": "^4.21.3",
|
||||
"@babel/compat-data": "^7.22.9",
|
||||
"@babel/helper-validator-option": "^7.22.15",
|
||||
"browserslist": "^4.21.9",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.0"
|
||||
"semver": "^6.3.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
|
||||
"integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
|
||||
"integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
|
||||
"integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.21.4"
|
||||
"@babel/types": "^7.22.15"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
|
||||
"integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
|
||||
"integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.2",
|
||||
"@babel/types": "^7.21.2"
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-module-imports": "^7.22.15",
|
||||
"@babel/helper-simple-access": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/helper-validator-identifier": "^7.22.20"
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
@@ -7815,67 +7833,67 @@
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
|
||||
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.2"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
|
||||
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz",
|
||||
"integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
|
||||
"integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz",
|
||||
"integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
|
||||
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.0",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz",
|
||||
"integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/plugin-syntax-async-generators": {
|
||||
@@ -8005,42 +8023,42 @@
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
|
||||
"integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/parser": "^7.20.7",
|
||||
"@babel/types": "^7.20.7"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz",
|
||||
"integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.21.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz",
|
||||
"integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -9397,9 +9415,9 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
|
||||
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
|
||||
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -9615,15 +9633,15 @@
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.21.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
|
||||
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
|
||||
"version": "4.22.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
||||
"integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001449",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"node-releases": "^2.0.8",
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
"caniuse-lite": "^1.0.30001541",
|
||||
"electron-to-chromium": "^1.4.535",
|
||||
"node-releases": "^2.0.13",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
}
|
||||
},
|
||||
"bs-logger": {
|
||||
@@ -9694,9 +9712,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001473",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
|
||||
"integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==",
|
||||
"version": "1.0.30001547",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz",
|
||||
"integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
@@ -9832,9 +9850,9 @@
|
||||
}
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
|
||||
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
@@ -9924,9 +9942,9 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.348",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz",
|
||||
"integrity": "sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ==",
|
||||
"version": "1.4.551",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
|
||||
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==",
|
||||
"dev": true
|
||||
},
|
||||
"emittery": {
|
||||
@@ -12280,9 +12298,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
|
||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-path": {
|
||||
@@ -13118,9 +13136,9 @@
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
|
||||
},
|
||||
"update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escalade": "^3.1.1",
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
"rust-build": "cd rust-gbt && npm run build-release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^18.15.3",
|
||||
"axios": "~1.4.0",
|
||||
"axios": "~1.5.0",
|
||||
"bitcoinjs-lib": "~6.1.3",
|
||||
"crypto-js": "~4.1.1",
|
||||
"crypto-js": "~4.2.0",
|
||||
"express": "~4.18.2",
|
||||
"maxmind": "~4.3.11",
|
||||
"mysql2": "~3.6.0",
|
||||
@@ -55,7 +55,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/express": "^4.17.17",
|
||||
|
||||
@@ -6,8 +6,6 @@ authors = ["mononaut"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"DATABASE": "__DATABASE_DATABASE__",
|
||||
"USERNAME": "__DATABASE_USERNAME__",
|
||||
"PASSWORD": "__DATABASE_PASSWORD__",
|
||||
"PID_DIR": "__DATABASE_PID_FILE__",
|
||||
"TIMEOUT": 3000
|
||||
},
|
||||
"SYSLOG": {
|
||||
|
||||
@@ -84,6 +84,7 @@ describe('Mempool Backend Config', () => {
|
||||
USERNAME: 'mempool',
|
||||
PASSWORD: 'mempool',
|
||||
TIMEOUT: 180000,
|
||||
PID_DIR: ''
|
||||
});
|
||||
|
||||
expect(config.SYSLOG).toStrictEqual({
|
||||
|
||||
@@ -478,7 +478,7 @@ class BitcoinRoutes {
|
||||
}
|
||||
|
||||
let nextHash = startFromHash;
|
||||
for (let i = 0; i < 10 && nextHash; i++) {
|
||||
for (let i = 0; i < 15 && nextHash; i++) {
|
||||
const localBlock = blocks.getBlocks().find((b) => b.id === nextHash);
|
||||
if (localBlock) {
|
||||
returnBlocks.push(localBlock);
|
||||
|
||||
@@ -75,9 +75,9 @@ class FailoverRouter {
|
||||
|
||||
const results = await Promise.allSettled(this.hosts.map(async (host) => {
|
||||
if (host.socket) {
|
||||
return this.pollConnection.get<number>('/blocks/tip/height', { socketPath: host.host, timeout: 2000 });
|
||||
return this.pollConnection.get<number>('/blocks/tip/height', { socketPath: host.host, timeout: 5000 });
|
||||
} else {
|
||||
return this.pollConnection.get<number>(host.host + '/blocks/tip/height', { timeout: 2000 });
|
||||
return this.pollConnection.get<number>(host.host + '/blocks/tip/height', { timeout: 5000 });
|
||||
}
|
||||
}));
|
||||
const maxHeight = results.reduce((max, result) => Math.max(max, result.status === 'fulfilled' ? result.value?.data || 0 : 0), 0);
|
||||
|
||||
@@ -29,10 +29,6 @@ import websocketHandler from './websocket-handler';
|
||||
import redisCache from './redis-cache';
|
||||
import rbfCache from './rbf-cache';
|
||||
import { calcBitsDifference } from './difficulty-adjustment';
|
||||
import os from 'os';
|
||||
import { Worker } from 'worker_threads';
|
||||
import path from 'path';
|
||||
|
||||
|
||||
class Blocks {
|
||||
private blocks: BlockExtended[] = [];
|
||||
@@ -410,8 +406,6 @@ class Blocks {
|
||||
return;
|
||||
}
|
||||
|
||||
const workerPool: Worker[] = [];
|
||||
|
||||
try {
|
||||
// Get all indexed block hash
|
||||
const indexedBlocks = await blocksRepository.$getIndexedBlocks();
|
||||
@@ -426,67 +420,39 @@ class Blocks {
|
||||
let newlyIndexed = 0;
|
||||
let totalIndexed = indexedBlockSummariesHashesArray.length;
|
||||
let indexedThisRun = 0;
|
||||
let timer = Date.now();
|
||||
const startedAt = Date.now();
|
||||
let timer = Date.now() / 1000;
|
||||
const startedAt = Date.now() / 1000;
|
||||
|
||||
const blocksToIndex = indexedBlocks.filter(block => !indexedBlockSummariesHashes[block.hash]);
|
||||
|
||||
if (!blocksToIndex.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const numWorkers = Math.max(1, os.cpus().length - 1);
|
||||
for (let i = 0; i < numWorkers; i++) {
|
||||
workerPool.push(new Worker(path.resolve(__dirname, '../index-workers/block-summary-worker.js')));
|
||||
}
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
// This function assigns a task to a worker
|
||||
const assignTask = (worker: Worker): boolean => {
|
||||
if (blocksToIndex.length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
worker.postMessage(blocksToIndex.shift());
|
||||
return true;
|
||||
for (const block of indexedBlocks) {
|
||||
if (indexedBlockSummariesHashes[block.hash] === true) {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
const handleResult = (height: number): void => {
|
||||
// Logging
|
||||
const elapsedSeconds = (Date.now() / 1000) - timer;
|
||||
if (elapsedSeconds > 5) {
|
||||
const runningFor = (Date.now() / 1000) - startedAt;
|
||||
const blockPerSeconds = indexedThisRun / elapsedSeconds;
|
||||
const progress = Math.round(totalIndexed / indexedBlocks.length * 10000) / 100;
|
||||
logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor.toFixed(2)} seconds`, logger.tags.mining);
|
||||
timer = Date.now() / 1000;
|
||||
indexedThisRun = 0;
|
||||
}
|
||||
|
||||
|
||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
const txs = (await bitcoinApi.$getTxsForBlock(block.hash)).map(tx => transactionUtils.extendTransaction(tx));
|
||||
const cpfpSummary = await this.$indexCPFP(block.hash, block.height, txs);
|
||||
await this.$getStrippedBlockTransactions(block.hash, true, true, cpfpSummary, block.height); // This will index the block summary
|
||||
} else {
|
||||
await this.$getStrippedBlockTransactions(block.hash, true, true); // This will index the block summary
|
||||
}
|
||||
|
||||
// Logging
|
||||
indexedThisRun++;
|
||||
totalIndexed++;
|
||||
newlyIndexed++;
|
||||
const elapsed = Date.now() - timer;
|
||||
if (elapsed > 5000) {
|
||||
const runningFor = Date.now() - startedAt;
|
||||
const blockPerSeconds = indexedThisRun / (elapsed / 1000);
|
||||
const progress = Math.round(totalIndexed / indexedBlocks.length * 10000) / 100;
|
||||
logger.debug(`Indexing block summary for #${height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${(runningFor / 1000).toFixed(2)} seconds`, logger.tags.mining);
|
||||
timer = Date.now();
|
||||
indexedThisRun = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Start a task on each worker
|
||||
for (const worker of workerPool) {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
worker.removeAllListeners();
|
||||
worker.on('message', (result) => {
|
||||
// Handle the result, then assign a new task to the worker
|
||||
handleResult(result);
|
||||
if (!assignTask(worker)) {
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
worker.on('error', reject);
|
||||
if (!assignTask(worker)) {
|
||||
resolve();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
} else {
|
||||
@@ -495,14 +461,6 @@ class Blocks {
|
||||
} catch (e) {
|
||||
logger.err(`Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining);
|
||||
throw e;
|
||||
} finally {
|
||||
for (const worker of workerPool) {
|
||||
if (worker) {
|
||||
// clean up the workers
|
||||
worker.removeAllListeners();
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,7 +557,6 @@ class Blocks {
|
||||
* [INDEXING] Index all blocks metadata for the mining dashboard
|
||||
*/
|
||||
public async $generateBlockDatabase(): Promise<boolean> {
|
||||
const workerPool: Worker[] = [];
|
||||
try {
|
||||
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
||||
let currentBlockHeight = blockchainInfo.blocks;
|
||||
@@ -618,18 +575,12 @@ class Blocks {
|
||||
let totalIndexed = await blocksRepository.$blockCountBetweenHeight(currentBlockHeight, lastBlockToIndex);
|
||||
let indexedThisRun = 0;
|
||||
let newlyIndexed = 0;
|
||||
const startedAt = Date.now();
|
||||
let timer = Date.now();
|
||||
|
||||
if (currentBlockHeight >= lastBlockToIndex) {
|
||||
const numWorkers = Math.max(1, os.cpus().length - 1);
|
||||
for (let i = 0; i < numWorkers; i++) {
|
||||
workerPool.push(new Worker(path.resolve(__dirname, '../index-workers/block-worker.js')));
|
||||
}
|
||||
}
|
||||
const startedAt = Date.now() / 1000;
|
||||
let timer = Date.now() / 1000;
|
||||
|
||||
while (currentBlockHeight >= lastBlockToIndex) {
|
||||
const endBlock = Math.max(0, lastBlockToIndex, currentBlockHeight - chunkSize + 1);
|
||||
|
||||
const missingBlockHeights: number[] = await blocksRepository.$getMissingBlocksBetweenHeights(
|
||||
currentBlockHeight, endBlock);
|
||||
if (missingBlockHeights.length <= 0) {
|
||||
@@ -639,65 +590,33 @@ class Blocks {
|
||||
|
||||
logger.info(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`, logger.tags.mining);
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
// This function assigns a task to a worker
|
||||
const assignTask = (worker: Worker): boolean => {
|
||||
if (missingBlockHeights.length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
worker.postMessage({ height: missingBlockHeights.shift() });
|
||||
return true;
|
||||
for (const blockHeight of missingBlockHeights) {
|
||||
if (blockHeight < lastBlockToIndex) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleResult = (height: number): void => {
|
||||
indexedThisRun++;
|
||||
totalIndexed++;
|
||||
newlyIndexed++;
|
||||
const elapsed = Date.now() - timer;
|
||||
if (elapsed > 5000 || height === lastBlockToIndex) {
|
||||
const runningFor = Date.now() - startedAt;
|
||||
const blockPerSeconds = indexedThisRun / (elapsed / 1000);
|
||||
++indexedThisRun;
|
||||
++totalIndexed;
|
||||
const elapsedSeconds = (Date.now() / 1000) - timer;
|
||||
if (elapsedSeconds > 5 || blockHeight === lastBlockToIndex) {
|
||||
const runningFor = (Date.now() / 1000) - startedAt;
|
||||
const blockPerSeconds = indexedThisRun / elapsedSeconds;
|
||||
const progress = Math.round(totalIndexed / indexingBlockAmount * 10000) / 100;
|
||||
logger.debug(`Indexing block #${height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress.toFixed(2)}%) | elapsed: ${(runningFor / 1000).toFixed(2)} seconds`, logger.tags.mining);
|
||||
timer = Date.now();
|
||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress.toFixed(2)}%) | elapsed: ${runningFor.toFixed(2)} seconds`, logger.tags.mining);
|
||||
timer = Date.now() / 1000;
|
||||
indexedThisRun = 0;
|
||||
loadingIndicators.setProgress('block-indexing', progress, false);
|
||||
}
|
||||
};
|
||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
||||
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, null, true);
|
||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||
|
||||
// Start a task on each worker
|
||||
for (const worker of workerPool) {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
worker.removeAllListeners();
|
||||
worker.on('message', (result) => {
|
||||
// Handle the result, then assign a new task to the worker
|
||||
handleResult(result);
|
||||
if (!assignTask(worker)) {
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
worker.on('error', reject);
|
||||
if (!assignTask(worker)) {
|
||||
resolve();
|
||||
}
|
||||
}));
|
||||
newlyIndexed++;
|
||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
currentBlockHeight -= chunkSize;
|
||||
}
|
||||
|
||||
for (const worker of workerPool) {
|
||||
if (worker) {
|
||||
// clean up the workers
|
||||
worker.removeAllListeners();
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
} else {
|
||||
@@ -708,14 +627,6 @@ class Blocks {
|
||||
logger.err('Block indexing failed. Trying again in 10 seconds. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
loadingIndicators.setProgress('block-indexing', 100);
|
||||
throw e;
|
||||
} finally {
|
||||
for (const worker of workerPool) {
|
||||
if (worker) {
|
||||
// clean up the workers
|
||||
worker.removeAllListeners();
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await BlocksRepository.$validateChain();
|
||||
|
||||
@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 65;
|
||||
private static currentVersion = 66;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@@ -553,6 +553,11 @@ class DatabaseMigration {
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"');
|
||||
await this.updateToSchemaVersion(65);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 66) {
|
||||
await this.$executeQuery('ALTER TABLE `statistics` ADD min_fee FLOAT UNSIGNED DEFAULT NULL');
|
||||
await this.updateToSchemaVersion(66);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,12 @@ class NodesRoutes {
|
||||
switch (config.MEMPOOL.NETWORK) {
|
||||
case 'testnet':
|
||||
nodesList = [
|
||||
'0259db43b4e4ac0ff12a805f2d81e521253ba2317f6739bc611d8e2fa156d64256',
|
||||
'0352b9944b9a52bd2116c91f1ba70c4ef851ac5ba27e1b20f1d92da3ade010dd10',
|
||||
'03424f5a7601eaa47482cb17100b31a84a04d14fb44b83a57eeceffd8e299878e3',
|
||||
'032850492ee61a5f7006a2fda6925e4b4ec3782f2b6de2ff0e439ef5a38c3b2470',
|
||||
'022c80bace98831c44c32fb69755f2b353434e0ee9e7fbda29507f7ef8abea1421',
|
||||
'02c3559c833e6f99f9ca05fe503e0b4e7524dea9121344edfd3e811101e0c28680',
|
||||
'032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b',
|
||||
'025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7',
|
||||
'0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55',
|
||||
@@ -64,6 +70,12 @@ class NodesRoutes {
|
||||
break;
|
||||
case 'signet':
|
||||
nodesList = [
|
||||
'029fe3621fc0c6e08056a14b868f8fb9acca1aa28a129512f6cea0f0d7654d9f92',
|
||||
'02f60cd7a3a4f1c953dd9554a6ebd51a34f8b10b8124b7fc43a0b381139b55c883',
|
||||
'03cbbf581774700865eebd1be42d022bc004ba30881274ab304e088a25d70e773d',
|
||||
'0243348cb3741cfe2d8485fa8375c29c7bc7cbb67577c363cb6987a5e5fd0052cc',
|
||||
'02cb73e631af44bee600d80f8488a9194c9dc5c7590e575c421a070d1be05bc8e9',
|
||||
'0306f55ee631aa1e2cd4d9b2bfcbc14404faec5c541cef8b2e6f779061029d09c4',
|
||||
'03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956',
|
||||
'033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de',
|
||||
'02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781',
|
||||
@@ -86,6 +98,12 @@ class NodesRoutes {
|
||||
break;
|
||||
default:
|
||||
nodesList = [
|
||||
'02b12b889fe3c943cb05645921040ef13d6d397a2e7a4ad000e28500c505ff26d6',
|
||||
'0302240ac9d71b39617cbde2764837ec3d6198bd6074b15b75d2ff33108e89d2e1',
|
||||
'03364a8ace313376e5e4b68c954e287c6388e16df9e9fdbaf0363ecac41105cbf6',
|
||||
'03229ab4b7f692753e094b93df90530150680f86b535b5183b0cffd75b3df583fc',
|
||||
'03a696eb7acde991c1be97a58a9daef416659539ae462b897f5e9ae361f990228e',
|
||||
'0248bf26cf3a63ab8870f34dc0ec9e6c8c6288cdba96ba3f026f34ec0f13ac4055',
|
||||
'03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61',
|
||||
'03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437',
|
||||
'03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144',
|
||||
|
||||
@@ -31,7 +31,7 @@ class MemoryCache {
|
||||
}
|
||||
|
||||
private cleanup() {
|
||||
this.cache = this.cache.filter((cache) => cache.expires < (new Date()));
|
||||
this.cache = this.cache.filter((cache) => cache.expires > (new Date()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -451,6 +451,7 @@ class MempoolBlocks {
|
||||
private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] {
|
||||
for (const [txid, rate] of rates) {
|
||||
if (txid in mempool) {
|
||||
mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
|
||||
mempool[txid].effectiveFeePerVsize = rate;
|
||||
mempool[txid].cpfpChecked = false;
|
||||
}
|
||||
@@ -494,6 +495,9 @@ 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});
|
||||
}
|
||||
}
|
||||
@@ -531,12 +535,21 @@ class MempoolBlocks {
|
||||
|
||||
const acceleration = accelerations[txid];
|
||||
if (isAccelerated[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) {
|
||||
if (!mempoolTx.acceleration) {
|
||||
mempoolTx.cpfpDirty = true;
|
||||
}
|
||||
mempoolTx.acceleration = true;
|
||||
for (const ancestor of mempoolTx.ancestors || []) {
|
||||
if (!mempool[ancestor.txid].acceleration) {
|
||||
mempool[ancestor.txid].cpfpDirty = true;
|
||||
}
|
||||
mempool[ancestor.txid].acceleration = true;
|
||||
isAccelerated[ancestor.txid] = true;
|
||||
}
|
||||
} else {
|
||||
if (mempoolTx.acceleration) {
|
||||
mempoolTx.cpfpDirty = true;
|
||||
}
|
||||
delete mempoolTx.acceleration;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class RbfCache {
|
||||
private expiring: Map<string, number> = new Map();
|
||||
private cacheQueue: CacheEvent[] = [];
|
||||
|
||||
public init(): void {
|
||||
constructor() {
|
||||
setInterval(this.cleanup.bind(this), 1000 * 60 * 10);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,21 +23,24 @@ class RedisCache {
|
||||
private cacheQueue: MempoolTransactionExtended[] = [];
|
||||
private txFlushLimit: number = 10000;
|
||||
|
||||
constructor() {
|
||||
if (config.REDIS.ENABLED) {
|
||||
const redisConfig = {
|
||||
socket: {
|
||||
path: config.REDIS.UNIX_SOCKET_PATH
|
||||
},
|
||||
database: NetworkDB[config.MEMPOOL.NETWORK],
|
||||
};
|
||||
this.client = createClient(redisConfig);
|
||||
this.client.on('error', (e) => {
|
||||
logger.err(`Error in Redis client: ${e instanceof Error ? e.message : e}`);
|
||||
});
|
||||
this.$ensureConnected();
|
||||
}
|
||||
}
|
||||
|
||||
private async $ensureConnected(): Promise<void> {
|
||||
if (!this.connected && config.REDIS.ENABLED) {
|
||||
if (!this.client) {
|
||||
const redisConfig = {
|
||||
socket: {
|
||||
path: config.REDIS.UNIX_SOCKET_PATH
|
||||
},
|
||||
database: NetworkDB[config.MEMPOOL.NETWORK],
|
||||
};
|
||||
this.client = createClient(redisConfig);
|
||||
this.client.on('error', (e) => {
|
||||
logger.err(`Error in Redis client: ${e instanceof Error ? e.message : e}`);
|
||||
});
|
||||
}
|
||||
|
||||
return this.client.connect().then(async () => {
|
||||
this.connected = true;
|
||||
logger.info(`Redis client connected`);
|
||||
|
||||
@@ -15,6 +15,7 @@ class StatisticsApi {
|
||||
mempool_byte_weight,
|
||||
fee_data,
|
||||
total_fee,
|
||||
min_fee,
|
||||
vsize_1,
|
||||
vsize_2,
|
||||
vsize_3,
|
||||
@@ -54,7 +55,7 @@ class StatisticsApi {
|
||||
vsize_1800,
|
||||
vsize_2000
|
||||
)
|
||||
VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)`;
|
||||
const [result]: any = await DB.query(query);
|
||||
return result.insertId;
|
||||
@@ -73,6 +74,7 @@ class StatisticsApi {
|
||||
mempool_byte_weight,
|
||||
fee_data,
|
||||
total_fee,
|
||||
min_fee,
|
||||
vsize_1,
|
||||
vsize_2,
|
||||
vsize_3,
|
||||
@@ -112,7 +114,7 @@ class StatisticsApi {
|
||||
vsize_1800,
|
||||
vsize_2000
|
||||
)
|
||||
VALUES (${statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
VALUES (${statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
const params: (string | number)[] = [
|
||||
@@ -122,6 +124,7 @@ class StatisticsApi {
|
||||
statistics.mempool_byte_weight,
|
||||
statistics.fee_data,
|
||||
statistics.total_fee,
|
||||
statistics.min_fee,
|
||||
statistics.vsize_1,
|
||||
statistics.vsize_2,
|
||||
statistics.vsize_3,
|
||||
@@ -171,7 +174,9 @@ class StatisticsApi {
|
||||
private getQueryForDaysAvg(div: number, interval: string) {
|
||||
return `SELECT
|
||||
UNIX_TIMESTAMP(added) as added,
|
||||
CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions,
|
||||
CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
|
||||
CAST(avg(min_fee) as DOUBLE) as min_fee,
|
||||
CAST(avg(vsize_1) as DOUBLE) as vsize_1,
|
||||
CAST(avg(vsize_2) as DOUBLE) as vsize_2,
|
||||
CAST(avg(vsize_3) as DOUBLE) as vsize_3,
|
||||
@@ -219,7 +224,9 @@ class StatisticsApi {
|
||||
private getQueryForDays(div: number, interval: string) {
|
||||
return `SELECT
|
||||
UNIX_TIMESTAMP(added) as added,
|
||||
CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions,
|
||||
CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
|
||||
CAST(avg(min_fee) as DOUBLE) as min_fee,
|
||||
vsize_1,
|
||||
vsize_2,
|
||||
vsize_3,
|
||||
@@ -401,9 +408,11 @@ class StatisticsApi {
|
||||
return statistic.map((s) => {
|
||||
return {
|
||||
added: s.added,
|
||||
count: s.unconfirmed_transactions,
|
||||
vbytes_per_second: s.vbytes_per_second,
|
||||
mempool_byte_weight: s.mempool_byte_weight,
|
||||
total_fee: s.total_fee,
|
||||
min_fee: s.min_fee,
|
||||
vsizes: [
|
||||
s.vsize_1,
|
||||
s.vsize_2,
|
||||
|
||||
@@ -89,6 +89,9 @@ class Statistics {
|
||||
}
|
||||
});
|
||||
|
||||
// get minFee and convert to sats/vb
|
||||
const minFee = memPool.getMempoolInfo().mempoolminfee * 100000;
|
||||
|
||||
try {
|
||||
const insertId = await statisticsApi.$create({
|
||||
added: 'NOW()',
|
||||
@@ -98,6 +101,7 @@ class Statistics {
|
||||
mempool_byte_weight: totalWeight,
|
||||
total_fee: totalFee,
|
||||
fee_data: '',
|
||||
min_fee: minFee,
|
||||
vsize_1: weightVsizeFees['1'] || 0,
|
||||
vsize_2: weightVsizeFees['2'] || 0,
|
||||
vsize_3: weightVsizeFees['3'] || 0,
|
||||
|
||||
@@ -5,6 +5,7 @@ import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||
import logger from '../logger';
|
||||
import config from '../config';
|
||||
import pLimit from '../utils/p-limit';
|
||||
|
||||
class TransactionUtils {
|
||||
constructor() { }
|
||||
@@ -74,8 +75,12 @@ class TransactionUtils {
|
||||
|
||||
public async $getMempoolTransactionsExtended(txids: string[], addPrevouts = false, lazyPrevouts = false, forceCore = false): Promise<MempoolTransactionExtended[]> {
|
||||
if (forceCore || config.MEMPOOL.BACKEND !== 'esplora') {
|
||||
const results = await Promise.allSettled(txids.map(txid => this.$getTransactionExtended(txid, addPrevouts, lazyPrevouts, forceCore, true)));
|
||||
return (results.filter(r => r.status === 'fulfilled') as PromiseFulfilledResult<MempoolTransactionExtended>[]).map(r => r.value);
|
||||
const limiter = pLimit(8); // Run 8 requests at a time
|
||||
const results = await Promise.allSettled(txids.map(
|
||||
txid => limiter(() => this.$getMempoolTransactionExtended(txid, addPrevouts, lazyPrevouts, forceCore))
|
||||
));
|
||||
return results.filter(reply => reply.status === 'fulfilled')
|
||||
.map(r => (r as PromiseFulfilledResult<MempoolTransactionExtended>).value);
|
||||
} else {
|
||||
const transactions = await bitcoinApi.$getMempoolTransactions(txids);
|
||||
return transactions.map(transaction => {
|
||||
|
||||
@@ -486,6 +486,7 @@ class WebsocketHandler {
|
||||
|
||||
// pre-compute address transactions
|
||||
const addressCache = this.makeAddressCache(newTransactions);
|
||||
const removedAddressCache = this.makeAddressCache(deletedTransactions);
|
||||
|
||||
this.wss.clients.forEach(async (client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
@@ -526,11 +527,15 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (client['track-address']) {
|
||||
const foundTransactions = Array.from(addressCache[client['track-address']]?.values() || []);
|
||||
const newTransactions = Array.from(addressCache[client['track-address']]?.values() || []);
|
||||
const removedTransactions = Array.from(removedAddressCache[client['track-address']]?.values() || []);
|
||||
// txs may be missing prevouts in non-esplora backends
|
||||
// so fetch the full transactions now
|
||||
const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(foundTransactions) : foundTransactions;
|
||||
const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(newTransactions) : newTransactions;
|
||||
|
||||
if (removedTransactions.length) {
|
||||
response['address-removed-transactions'] = JSON.stringify(removedTransactions);
|
||||
}
|
||||
if (fullTransactions.length) {
|
||||
response['address-transactions'] = JSON.stringify(fullTransactions);
|
||||
}
|
||||
@@ -586,13 +591,25 @@ class WebsocketHandler {
|
||||
|
||||
const mempoolTx = newMempool[trackTxid];
|
||||
if (mempoolTx && mempoolTx.position) {
|
||||
response['txPosition'] = JSON.stringify({
|
||||
const positionData = {
|
||||
txid: trackTxid,
|
||||
position: {
|
||||
...mempoolTx.position,
|
||||
accelerated: mempoolTx.acceleration || undefined,
|
||||
}
|
||||
});
|
||||
};
|
||||
if (mempoolTx.cpfpDirty) {
|
||||
positionData['cpfp'] = {
|
||||
ancestors: mempoolTx.ancestors,
|
||||
bestDescendant: mempoolTx.bestDescendant || null,
|
||||
descendants: mempoolTx.descendants || null,
|
||||
effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null,
|
||||
sigops: mempoolTx.sigops,
|
||||
adjustedVsize: mempoolTx.adjustedVsize,
|
||||
acceleration: mempoolTx.acceleration
|
||||
};
|
||||
}
|
||||
response['txPosition'] = JSON.stringify(positionData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ interface IConfig {
|
||||
USERNAME: string;
|
||||
PASSWORD: string;
|
||||
TIMEOUT: number;
|
||||
PID_DIR: string;
|
||||
};
|
||||
SYSLOG: {
|
||||
ENABLED: boolean;
|
||||
@@ -219,6 +220,7 @@ const defaults: IConfig = {
|
||||
'USERNAME': 'mempool',
|
||||
'PASSWORD': 'mempool',
|
||||
'TIMEOUT': 180000,
|
||||
'PID_DIR': '',
|
||||
},
|
||||
'SYSLOG': {
|
||||
'ENABLED': true,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
import config from './config';
|
||||
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
|
||||
import logger from './logger';
|
||||
@@ -101,6 +103,33 @@ import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } fr
|
||||
}
|
||||
}
|
||||
|
||||
public getPidLock(): boolean {
|
||||
const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const pid = fs.readFileSync(filePath).toString();
|
||||
if (pid !== `${process.pid}`) {
|
||||
const msg = `Already running on PID ${pid} (or pid file '${filePath}' is stale)`;
|
||||
logger.err(msg);
|
||||
throw new Error(msg);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
fs.writeFileSync(filePath, `${process.pid}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public releasePidLock(): void {
|
||||
const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const pid = fs.readFileSync(filePath).toString();
|
||||
if (pid === `${process.pid}`) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getPool(): Promise<Pool> {
|
||||
if (this.pool === null) {
|
||||
this.pool = createPool(this.poolConfig);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { parentPort } from 'worker_threads';
|
||||
import bitcoinApi from '../api/bitcoin/bitcoin-api-factory';
|
||||
import blocks from '../api/blocks';
|
||||
import config from '../config';
|
||||
import transactionUtils from '../api/transaction-utils';
|
||||
import bitcoinClient from '../api/bitcoin/bitcoin-client';
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on('message', async ({ hash, height }) => {
|
||||
if (hash != null && height != null) {
|
||||
await indexBlockSummary(hash, height);
|
||||
}
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.postMessage(height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function indexBlockSummary(hash: string, height: number): Promise<void> {
|
||||
let txs;
|
||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
txs = (await bitcoinApi.$getTxsForBlock(hash)).map(tx => transactionUtils.extendTransaction(tx));
|
||||
} else {
|
||||
const block = await bitcoinClient.getBlock(hash, 2);
|
||||
txs = block.tx.map(tx => {
|
||||
tx.fee = Math.round(tx.fee * 100_000_000);
|
||||
tx.vout.forEach((vout) => {
|
||||
vout.value = Math.round(vout.value * 100000000);
|
||||
});
|
||||
tx.vsize = Math.round(tx.weight / 4); // required for backwards compatibility
|
||||
return tx;
|
||||
});
|
||||
}
|
||||
|
||||
const cpfpSummary = await blocks.$indexCPFP(hash, height, txs);
|
||||
await blocks.$getStrippedBlockTransactions(hash, true, true, cpfpSummary, height); // This will index the block summary
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { parentPort } from 'worker_threads';
|
||||
import bitcoinApi from '../api/bitcoin/bitcoin-api-factory';
|
||||
import blocksRepository from '../repositories/BlocksRepository';
|
||||
import blocks from '../api/blocks';
|
||||
import { IEsploraApi } from '../api/bitcoin/esplora-api.interface';
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on('message', async (params) => {
|
||||
if (params.height != null) {
|
||||
await indexBlock(params.height);
|
||||
}
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.postMessage(params.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function indexBlock(blockHeight: number): Promise<void> {
|
||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
||||
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
|
||||
const transactions = await blocks['$getTransactionsExtended'](blockHash, block.height, true, null, true);
|
||||
const blockExtended = await blocks['$getBlockExtended'](block, transactions);
|
||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||
}
|
||||
@@ -43,7 +43,6 @@ import { AxiosError } from 'axios';
|
||||
import v8 from 'v8';
|
||||
import { formatBytes, getBytesUnit } from './utils/format';
|
||||
import redisCache from './api/redis-cache';
|
||||
import rbfCache from './api/rbf-cache';
|
||||
|
||||
class Server {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
@@ -92,11 +91,18 @@ class Server {
|
||||
async startServer(worker = false): Promise<void> {
|
||||
logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`);
|
||||
|
||||
// Register cleanup listeners for exit events
|
||||
['exit', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'unhandledRejection'].forEach(event => {
|
||||
process.on(event, () => { this.onExit(event); });
|
||||
});
|
||||
|
||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
bitcoinApi.startHealthChecks();
|
||||
}
|
||||
|
||||
if (config.DATABASE.ENABLED) {
|
||||
DB.getPidLock();
|
||||
|
||||
await DB.checkDbConnection();
|
||||
try {
|
||||
if (process.env.npm_config_reindex_blocks === 'true') { // Re-index requests
|
||||
@@ -108,8 +114,6 @@ class Server {
|
||||
}
|
||||
}
|
||||
|
||||
rbfCache.init();
|
||||
|
||||
this.app
|
||||
.use((req: Request, res: Response, next: NextFunction) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
@@ -309,6 +313,15 @@ class Server {
|
||||
this.lastHeapLogTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
onExit(exitEvent): void {
|
||||
if (config.DATABASE.ENABLED) {
|
||||
DB.releasePidLock();
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
((): Server => new Server())();
|
||||
|
||||
@@ -104,6 +104,7 @@ export interface MempoolTransactionExtended extends TransactionExtended {
|
||||
adjustedFeePerVsize: number;
|
||||
inputs?: number[];
|
||||
lastBoosted?: number;
|
||||
cpfpDirty?: boolean;
|
||||
}
|
||||
|
||||
export interface AuditTransaction {
|
||||
@@ -299,6 +300,7 @@ export interface Statistic {
|
||||
total_fee: number;
|
||||
mempool_byte_weight: number;
|
||||
fee_data: string;
|
||||
min_fee: number;
|
||||
|
||||
vsize_1: number;
|
||||
vsize_2: number;
|
||||
@@ -345,6 +347,7 @@ export interface OptimizedStatistic {
|
||||
vbytes_per_second: number;
|
||||
total_fee: number;
|
||||
mempool_byte_weight: number;
|
||||
min_fee: number;
|
||||
vsizes: number[];
|
||||
}
|
||||
|
||||
|
||||
179
backend/src/utils/p-limit.ts
Normal file
179
backend/src/utils/p-limit.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies
|
||||
or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
How it works:
|
||||
`this._head` is an instance of `Node` which keeps track of its current value and nests
|
||||
another instance of `Node` that keeps the value that comes after it. When a value is
|
||||
provided to `.enqueue()`, the code needs to iterate through `this._head`, going deeper
|
||||
and deeper to find the last value. However, iterating through every single item is slow.
|
||||
This problem is solved by saving a reference to the last value as `this._tail` so that
|
||||
it can reference it to add a new value.
|
||||
*/
|
||||
|
||||
class Node {
|
||||
value;
|
||||
next;
|
||||
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class Queue {
|
||||
private _head;
|
||||
private _tail;
|
||||
private _size;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
enqueue(value) {
|
||||
const node = new Node(value);
|
||||
|
||||
if (this._head) {
|
||||
this._tail.next = node;
|
||||
this._tail = node;
|
||||
} else {
|
||||
this._head = node;
|
||||
this._tail = node;
|
||||
}
|
||||
|
||||
this._size++;
|
||||
}
|
||||
|
||||
dequeue() {
|
||||
const current = this._head;
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._head = this._head.next;
|
||||
this._size--;
|
||||
return current.value;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
let current = this._head;
|
||||
|
||||
while (current) {
|
||||
yield current.value;
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface LimitFunction {
|
||||
readonly activeCount: number;
|
||||
readonly pendingCount: number;
|
||||
clearQueue: () => void;
|
||||
<Arguments extends unknown[], ReturnType>(
|
||||
fn: (...args: Arguments) => PromiseLike<ReturnType> | ReturnType,
|
||||
...args: Arguments
|
||||
): Promise<ReturnType>;
|
||||
}
|
||||
|
||||
export default function pLimit(concurrency: number): LimitFunction {
|
||||
if (
|
||||
!(
|
||||
(Number.isInteger(concurrency) ||
|
||||
concurrency === Number.POSITIVE_INFINITY) &&
|
||||
concurrency > 0
|
||||
)
|
||||
) {
|
||||
throw new TypeError('Expected `concurrency` to be a number from 1 and up');
|
||||
}
|
||||
|
||||
const queue = new Queue();
|
||||
let activeCount = 0;
|
||||
|
||||
const next = () => {
|
||||
activeCount--;
|
||||
|
||||
if (queue.size > 0) {
|
||||
queue.dequeue()();
|
||||
}
|
||||
};
|
||||
|
||||
const run = async (fn, resolve, args) => {
|
||||
activeCount++;
|
||||
|
||||
const result = (async () => fn(...args))();
|
||||
|
||||
resolve(result);
|
||||
|
||||
try {
|
||||
await result;
|
||||
} catch {}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const enqueue = (fn, resolve, args) => {
|
||||
queue.enqueue(run.bind(undefined, fn, resolve, args));
|
||||
|
||||
(async () => {
|
||||
// This function needs to wait until the next microtask before comparing
|
||||
// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
|
||||
// when the run function is dequeued and called. The comparison in the if-statement
|
||||
// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
|
||||
await Promise.resolve();
|
||||
|
||||
if (activeCount < concurrency && queue.size > 0) {
|
||||
queue.dequeue()();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const generator = (fn, ...args) =>
|
||||
new Promise((resolve) => {
|
||||
enqueue(fn, resolve, args);
|
||||
});
|
||||
|
||||
Object.defineProperties(generator, {
|
||||
activeCount: {
|
||||
get: () => activeCount,
|
||||
},
|
||||
pendingCount: {
|
||||
get: () => queue.size,
|
||||
},
|
||||
clearQueue: {
|
||||
value: () => {
|
||||
queue.clear();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return generator as any;
|
||||
}
|
||||
3
contributors/TKone7.txt
Normal file
3
contributors/TKone7.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of October 23, 2023.
|
||||
|
||||
Signed: TKone7
|
||||
3
contributors/fubz.txt
Normal file
3
contributors/fubz.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022.
|
||||
|
||||
Signed: fubz
|
||||
3
contributors/orangesurf.txt
Normal file
3
contributors/orangesurf.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of September 12, 2023.
|
||||
|
||||
Signed: orange surf
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:16.16.0-buster-slim AS builder
|
||||
FROM node:20.8.0-buster-slim AS builder
|
||||
|
||||
ARG commitHash
|
||||
ENV MEMPOOL_COMMIT_HASH=${commitHash}
|
||||
@@ -17,7 +17,7 @@ ENV PATH="/root/.cargo/bin:$PATH"
|
||||
RUN npm install --omit=dev --omit=optional
|
||||
RUN npm run package
|
||||
|
||||
FROM node:16.16.0-buster-slim
|
||||
FROM node:20.8.0-buster-slim
|
||||
|
||||
WORKDIR /backend
|
||||
|
||||
|
||||
@@ -69,7 +69,8 @@
|
||||
"DATABASE": "__DATABASE_DATABASE__",
|
||||
"USERNAME": "__DATABASE_USERNAME__",
|
||||
"PASSWORD": "__DATABASE_PASSWORD__",
|
||||
"TIMEOUT": __DATABASE_TIMEOUT__
|
||||
"TIMEOUT": __DATABASE_TIMEOUT__,
|
||||
"PID_DIR": "__DATABASE_PID_DIR__"
|
||||
},
|
||||
"SYSLOG": {
|
||||
"ENABLED": __SYSLOG_ENABLED__,
|
||||
|
||||
@@ -71,6 +71,7 @@ __DATABASE_DATABASE__=${DATABASE_DATABASE:=mempool}
|
||||
__DATABASE_USERNAME__=${DATABASE_USERNAME:=mempool}
|
||||
__DATABASE_PASSWORD__=${DATABASE_PASSWORD:=mempool}
|
||||
__DATABASE_TIMEOUT__=${DATABASE_TIMEOUT:=180000}
|
||||
__DATABASE_PID_DIR__=${DATABASE_PID_DIR:=""}
|
||||
|
||||
# SYSLOG
|
||||
__SYSLOG_ENABLED__=${SYSLOG_ENABLED:=false}
|
||||
@@ -139,7 +140,7 @@ __MEMPOOL_SERVICES_API__=${MEMPOOL_SERVICES_API:=""}
|
||||
__MEMPOOL_SERVICES_ACCELERATIONS__=${MEMPOOL_SERVICES_ACCELERATIONS:=false}
|
||||
|
||||
# REDIS
|
||||
__REDIS_ENABLED__=${REDIS_ENABLED:=true}
|
||||
__REDIS_ENABLED__=${REDIS_ENABLED:=false}
|
||||
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=true}
|
||||
|
||||
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
||||
@@ -209,6 +210,7 @@ sed -i "s!__DATABASE_DATABASE__!${__DATABASE_DATABASE__}!g" mempool-config.json
|
||||
sed -i "s!__DATABASE_USERNAME__!${__DATABASE_USERNAME__}!g" mempool-config.json
|
||||
sed -i "s!__DATABASE_PASSWORD__!${__DATABASE_PASSWORD__}!g" mempool-config.json
|
||||
sed -i "s!__DATABASE_TIMEOUT__!${__DATABASE_TIMEOUT__}!g" mempool-config.json
|
||||
sed -i "s!__DATABASE_PID_DIR__!${__DATABASE_PID_DIR__}!g" mempool-config.json
|
||||
|
||||
sed -i "s!__SYSLOG_ENABLED__!${__SYSLOG_ENABLED__}!g" mempool-config.json
|
||||
sed -i "s!__SYSLOG_HOST__!${__SYSLOG_HOST__}!g" mempool-config.json
|
||||
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
MYSQL_USER: "mempool"
|
||||
MYSQL_PASSWORD: "mempool"
|
||||
MYSQL_ROOT_PASSWORD: "admin"
|
||||
image: mariadb:10.5.8
|
||||
image: mariadb:10.5.21
|
||||
user: "1000:1000"
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
FROM ubuntu:18.04
|
||||
MAINTAINER mempool.space developers
|
||||
EXPOSE 50002
|
||||
|
||||
# runs as UID 1000 GID 1000 inside the container
|
||||
|
||||
ENV VERSION 4.0.9
|
||||
RUN set -x \
|
||||
&& apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends gpg gpg-agent dirmngr \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends wget xpra python3-pyqt5 python3-wheel python3-pip python3-setuptools libsecp256k1-0 libsecp256k1-dev python3-numpy python3-dev build-essential \
|
||||
&& wget -O /tmp/Electrum-${VERSION}.tar.gz https://download.electrum.org/${VERSION}/Electrum-${VERSION}.tar.gz \
|
||||
&& wget -O /tmp/Electrum-${VERSION}.tar.gz.asc https://download.electrum.org/${VERSION}/Electrum-${VERSION}.tar.gz.asc \
|
||||
&& gpg --keyserver keys.gnupg.net --recv-keys 6694D8DE7BE8EE5631BED9502BD5824B7F9470E6 \
|
||||
&& gpg --verify /tmp/Electrum-${VERSION}.tar.gz.asc /tmp/Electrum-${VERSION}.tar.gz \
|
||||
&& pip3 install /tmp/Electrum-${VERSION}.tar.gz \
|
||||
&& test -f /usr/local/bin/electrum \
|
||||
&& rm -vrf /tmp/Electrum-${VERSION}.tar.gz /tmp/Electrum-${VERSION}.tar.gz.asc ${HOME}/.gnupg \
|
||||
&& apt-get purge --autoremove -y python3-wheel python3-pip python3-setuptools python3-dev build-essential libsecp256k1-dev curl gpg gpg-agent dirmngr \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd -d /home/mempool -m mempool \
|
||||
&& mkdir /electrum \
|
||||
&& ln -s /electrum /home/mempool/.electrum \
|
||||
&& chown mempool:mempool /electrum
|
||||
|
||||
USER mempool
|
||||
ENV HOME /home/mempool
|
||||
WORKDIR /home/mempool
|
||||
VOLUME /electrum
|
||||
|
||||
CMD ["/usr/bin/xpra", "start", ":100", "--start-child=/usr/local/bin/electrum", "--bind-tcp=0.0.0.0:50002","--daemon=yes", "--notifications=no", "--mdns=no", "--pulseaudio=no", "--html=off", "--speaker=disabled", "--microphone=disabled", "--webcam=no", "--printing=no", "--dbus-launch=", "--exit-with-children"]
|
||||
ENTRYPOINT ["electrum"]
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:16.16.0-buster-slim AS builder
|
||||
FROM node:20.8.0-buster-slim AS builder
|
||||
|
||||
ARG commitHash
|
||||
ENV DOCKER_COMMIT_HASH=${commitHash}
|
||||
@@ -13,7 +13,7 @@ RUN npm install --omit=dev --omit=optional
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.17.8-alpine
|
||||
FROM nginx:1.24.0-alpine
|
||||
|
||||
WORKDIR /patch
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ __AUDIT__=${AUDIT:=false}
|
||||
__MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
__TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
__SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
__ACCELERATOR__=${ACCELERATOR:=false}
|
||||
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
|
||||
|
||||
# Export as environment variables to be used by envsubst
|
||||
@@ -65,6 +66,7 @@ export __AUDIT__
|
||||
export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
export __ACCELERATOR__
|
||||
export __HISTORICAL_PRICE__
|
||||
|
||||
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
|
||||
|
||||
462
frontend/package-lock.json
generated
462
frontend/package-lock.json
generated
@@ -59,9 +59,9 @@
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^12.17.2",
|
||||
"cypress-fail-on-console-error": "~4.0.3",
|
||||
"cypress-wait-until": "^2.0.0",
|
||||
"cypress": "^13.3.0",
|
||||
"cypress-fail-on-console-error": "~5.0.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"mock-socket": "~9.2.1",
|
||||
"start-server-and-test": "~2.0.0"
|
||||
}
|
||||
@@ -1250,11 +1250,12 @@
|
||||
"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg=="
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
||||
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.22.5"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1464,20 +1465,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
|
||||
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
|
||||
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name/node_modules/@babel/template": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1627,9 +1641,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -1669,12 +1683,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
|
||||
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1682,9 +1696,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
|
||||
"integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -2879,18 +2893,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.22.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
|
||||
"integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/generator": "^7.22.7",
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-function-name": "^7.22.5",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.22.7",
|
||||
"@babel/types": "^7.22.5",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -2898,13 +2912,36 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/@babel/generator": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
|
||||
"integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3016,9 +3053,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cypress/request": {
|
||||
"version": "2.88.11",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz",
|
||||
"integrity": "sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz",
|
||||
"integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
@@ -3034,9 +3071,9 @@
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.10.3",
|
||||
"qs": "6.10.4",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tough-cookie": "^4.1.3",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
@@ -4333,9 +4370,9 @@
|
||||
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
|
||||
"version": "18.17.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz",
|
||||
"integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw=="
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.5.0",
|
||||
@@ -5574,9 +5611,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
|
||||
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
@@ -5871,25 +5908,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserify-sign": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
|
||||
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
|
||||
"integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
|
||||
"dependencies": {
|
||||
"bn.js": "^5.1.1",
|
||||
"browserify-rsa": "^4.0.1",
|
||||
"bn.js": "^5.2.1",
|
||||
"browserify-rsa": "^4.1.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.5.3",
|
||||
"elliptic": "^6.5.4",
|
||||
"inherits": "^2.0.4",
|
||||
"parse-asn1": "^5.1.5",
|
||||
"readable-stream": "^3.6.0",
|
||||
"safe-buffer": "^5.2.0"
|
||||
"parse-asn1": "^5.1.6",
|
||||
"readable-stream": "^3.6.2",
|
||||
"safe-buffer": "^5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/browserify-sign/node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
@@ -7113,15 +7153,15 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "12.17.2",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.2.tgz",
|
||||
"integrity": "sha512-hxWAaWbqQBzzMuadSGSuQg5PDvIGOovm6xm0hIfpCVcORsCAj/gF2p0EvfnJ4f+jK2PCiDgP6D2eeE9/FK4Mjg==",
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz",
|
||||
"integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@cypress/request": "^2.88.11",
|
||||
"@cypress/request": "^3.0.0",
|
||||
"@cypress/xvfb": "^1.2.4",
|
||||
"@types/node": "^14.14.31",
|
||||
"@types/node": "^18.17.5",
|
||||
"@types/sinonjs__fake-timers": "8.1.1",
|
||||
"@types/sizzle": "^2.3.2",
|
||||
"arch": "^2.2.0",
|
||||
@@ -7154,6 +7194,7 @@
|
||||
"minimist": "^1.2.8",
|
||||
"ospath": "^1.2.2",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"process": "^0.11.10",
|
||||
"proxy-from-env": "1.0.0",
|
||||
"request-progress": "^3.0.0",
|
||||
"semver": "^7.5.3",
|
||||
@@ -7166,13 +7207,13 @@
|
||||
"cypress": "bin/cypress"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
|
||||
"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cypress-fail-on-console-error": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-4.0.3.tgz",
|
||||
"integrity": "sha512-v2nPupd2brtxKLkDQX58SbEPWRF/2nDbqPTnYyhPIYHqG7U3P2dGUZ3zraETKKoLhU3+C0otjgB6Vg/bHhocQw==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz",
|
||||
"integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"chai": "^4.3.4",
|
||||
@@ -7182,19 +7223,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cypress-wait-until": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.0.tgz",
|
||||
"integrity": "sha512-ulUZyrWBn+OuC8oiQuGKAScDYfpaWnE3dEE/raUo64w4RHQxZrQ/iMIWT4ZjGMMPr3P+BFEALCRnjQeRqzZj6g==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=18.16.0",
|
||||
"npm": ">=9.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cypress/node_modules/@types/node": {
|
||||
"version": "14.18.53",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.53.tgz",
|
||||
"integrity": "sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.1.tgz",
|
||||
"integrity": "sha512-+IyVnYNiaX1+C+V/LazrJWAi/CqiwfNoRSrFviECQEyolW1gDRy765PZosL2alSSGK8V10Y7BGfOQyZUDgmnjQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cypress/node_modules/ansi-styles": {
|
||||
@@ -10976,28 +11007,6 @@
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
||||
},
|
||||
"node_modules/jsdom/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==",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
@@ -15682,16 +15691,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"optional": true,
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie/node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
@@ -17790,11 +17808,12 @@
|
||||
"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg=="
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
||||
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.22.5"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
@@ -17959,17 +17978,29 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
|
||||
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q=="
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA=="
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
|
||||
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/template": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
@@ -18071,9 +18102,9 @@
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ=="
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.22.5",
|
||||
@@ -18101,19 +18132,19 @@
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
|
||||
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
|
||||
"integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q=="
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw=="
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
"version": "7.22.5",
|
||||
@@ -18890,29 +18921,51 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.22.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
|
||||
"integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/generator": "^7.22.7",
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-function-name": "^7.22.5",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.22.7",
|
||||
"@babel/types": "^7.22.5",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
|
||||
"integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -19010,9 +19063,9 @@
|
||||
}
|
||||
},
|
||||
"@cypress/request": {
|
||||
"version": "2.88.11",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz",
|
||||
"integrity": "sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz",
|
||||
"integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
@@ -19028,9 +19081,9 @@
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.10.3",
|
||||
"qs": "6.10.4",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tough-cookie": "^4.1.3",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
@@ -19927,9 +19980,9 @@
|
||||
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
|
||||
"version": "18.17.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz",
|
||||
"integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw=="
|
||||
},
|
||||
"@types/qrcode": {
|
||||
"version": "1.5.0",
|
||||
@@ -20868,9 +20921,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
|
||||
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.20.1",
|
||||
@@ -21214,25 +21267,25 @@
|
||||
}
|
||||
},
|
||||
"browserify-sign": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
|
||||
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
|
||||
"integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
|
||||
"requires": {
|
||||
"bn.js": "^5.1.1",
|
||||
"browserify-rsa": "^4.0.1",
|
||||
"bn.js": "^5.2.1",
|
||||
"browserify-rsa": "^4.1.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.5.3",
|
||||
"elliptic": "^6.5.4",
|
||||
"inherits": "^2.0.4",
|
||||
"parse-asn1": "^5.1.5",
|
||||
"readable-stream": "^3.6.0",
|
||||
"safe-buffer": "^5.2.0"
|
||||
"parse-asn1": "^5.1.6",
|
||||
"readable-stream": "^3.6.2",
|
||||
"safe-buffer": "^5.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
@@ -22065,14 +22118,14 @@
|
||||
"peer": true
|
||||
},
|
||||
"cypress": {
|
||||
"version": "12.17.2",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.2.tgz",
|
||||
"integrity": "sha512-hxWAaWbqQBzzMuadSGSuQg5PDvIGOovm6xm0hIfpCVcORsCAj/gF2p0EvfnJ4f+jK2PCiDgP6D2eeE9/FK4Mjg==",
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz",
|
||||
"integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@cypress/request": "^2.88.11",
|
||||
"@cypress/request": "^3.0.0",
|
||||
"@cypress/xvfb": "^1.2.4",
|
||||
"@types/node": "^14.14.31",
|
||||
"@types/node": "^18.17.5",
|
||||
"@types/sinonjs__fake-timers": "8.1.1",
|
||||
"@types/sizzle": "^2.3.2",
|
||||
"arch": "^2.2.0",
|
||||
@@ -22105,6 +22158,7 @@
|
||||
"minimist": "^1.2.8",
|
||||
"ospath": "^1.2.2",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"process": "^0.11.10",
|
||||
"proxy-from-env": "1.0.0",
|
||||
"request-progress": "^3.0.0",
|
||||
"semver": "^7.5.3",
|
||||
@@ -22114,12 +22168,6 @@
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "14.18.53",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.53.tgz",
|
||||
"integrity": "sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A==",
|
||||
"optional": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@@ -22236,9 +22284,9 @@
|
||||
}
|
||||
},
|
||||
"cypress-fail-on-console-error": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-4.0.3.tgz",
|
||||
"integrity": "sha512-v2nPupd2brtxKLkDQX58SbEPWRF/2nDbqPTnYyhPIYHqG7U3P2dGUZ3zraETKKoLhU3+C0otjgB6Vg/bHhocQw==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz",
|
||||
"integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chai": "^4.3.4",
|
||||
@@ -22248,9 +22296,9 @@
|
||||
}
|
||||
},
|
||||
"cypress-wait-until": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.0.tgz",
|
||||
"integrity": "sha512-ulUZyrWBn+OuC8oiQuGKAScDYfpaWnE3dEE/raUo64w4RHQxZrQ/iMIWT4ZjGMMPr3P+BFEALCRnjQeRqzZj6g==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.1.tgz",
|
||||
"integrity": "sha512-+IyVnYNiaX1+C+V/LazrJWAi/CqiwfNoRSrFviECQEyolW1gDRy765PZosL2alSSGK8V10Y7BGfOQyZUDgmnjQ==",
|
||||
"optional": true
|
||||
},
|
||||
"d": {
|
||||
@@ -24967,22 +25015,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
||||
},
|
||||
"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==",
|
||||
"requires": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -28497,13 +28529,21 @@
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"optional": true,
|
||||
"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==",
|
||||
"requires": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging",
|
||||
"start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed",
|
||||
"build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets && npm run build-mempool.js",
|
||||
"sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources'",
|
||||
"sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'",
|
||||
"sync-assets-dev": "node sync-assets.js 'src/resources/'",
|
||||
"generate-config": "node generate-config.js",
|
||||
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
|
||||
@@ -111,9 +111,9 @@
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^12.17.2",
|
||||
"cypress-fail-on-console-error": "~4.0.3",
|
||||
"cypress-wait-until": "^2.0.0",
|
||||
"cypress": "^13.3.0",
|
||||
"cypress-fail-on-console-error": "~5.0.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"mock-socket": "~9.2.1",
|
||||
"start-server-and-test": "~2.0.0"
|
||||
},
|
||||
|
||||
@@ -112,6 +112,14 @@ PROXY_CONFIG.push(...[
|
||||
"^/testnet": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
context: ['/api/v1/services/**'],
|
||||
target: `http://localhost:9000`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
{
|
||||
context: ['/api/v1/**'],
|
||||
target: `http://127.0.0.1:8999`,
|
||||
|
||||
@@ -112,6 +112,14 @@ PROXY_CONFIG.push(...[
|
||||
"^/testnet": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
context: ['/api/v1/services/**'],
|
||||
target: `http://localhost:9000`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
{
|
||||
context: ['/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
|
||||
@@ -95,6 +95,14 @@ if (configContent && configContent.BASE_MODULE === 'bisq') {
|
||||
}
|
||||
|
||||
PROXY_CONFIG.push(...[
|
||||
{
|
||||
context: ['/api/v1/services/**'],
|
||||
target: `http://localhost:9000`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
{
|
||||
context: ['/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
|
||||
@@ -41,6 +41,7 @@ export class BisqAddressComponent implements OnInit, OnDestroy {
|
||||
document.body.scrollTo(0, 0);
|
||||
this.addressString = params.get('id') || '';
|
||||
this.seoService.setTitle($localize`:@@bisq-address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.address:See current balance, pending transactions, and history of confirmed transactions for BSQ address ${this.addressString}:INTERPOLATION:.`);
|
||||
|
||||
return this.bisqApiService.getAddress$(this.addressString)
|
||||
.pipe(
|
||||
|
||||
@@ -69,6 +69,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy {
|
||||
this.location.replaceState(
|
||||
this.router.createUrlTree(['/bisq/block/', hash]).toString()
|
||||
);
|
||||
this.seoService.updateCanonical(this.location.path());
|
||||
return this.bisqApiService.getBlock$(this.blockHash)
|
||||
.pipe(catchError(this.caughtHttpError.bind(this)));
|
||||
}),
|
||||
@@ -88,6 +89,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy {
|
||||
this.isLoading = false;
|
||||
this.blockHeight = block.height;
|
||||
this.seoService.setTitle($localize`:@@bisq-block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.hash}:BLOCK_HASH:`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.block:See all BSQ transactions in Bitcoin block ${block.height}:BLOCK_HEIGHT: (block hash ${block.hash}:BLOCK_HASH:).`);
|
||||
this.block = block;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export class BisqBlocksComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
this.websocketService.want(['blocks']);
|
||||
this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.blocks:See a list of recent Bitcoin blocks with BSQ transactions, total BSQ sent per block, and more.`);
|
||||
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
|
||||
this.loadingItems = Array(this.itemsPerPage);
|
||||
if (document.body.clientWidth < 670) {
|
||||
|
||||
@@ -29,7 +29,8 @@ export class BisqDashboardComponent implements OnInit {
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle(`Markets`);
|
||||
this.seoService.setTitle($localize`:@@meta.title.bisq.markets:Markets`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.markets:Explore the full Bitcoin ecosystem with The Mempool Open Project™. See Bisq market prices, trading activity, and more.`);
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.volumes$ = this.bisqApiService.getAllVolumesDay$()
|
||||
|
||||
@@ -34,6 +34,7 @@ export class BisqMainDashboardComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.resetTitle();
|
||||
this.seoService.resetDescription();
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.usdPrice$ = this.stateService.conversions$.asObservable().pipe(
|
||||
|
||||
@@ -48,7 +48,8 @@ export class BisqMarketComponent implements OnInit, OnDestroy {
|
||||
map(([markets, routeParams]) => {
|
||||
const pair = routeParams.get('pair');
|
||||
const pairUpperCase = pair.replace('_', '/').toUpperCase();
|
||||
this.seoService.setTitle(`Bisq market: ${pairUpperCase}`);
|
||||
this.seoService.setTitle($localize`:@@meta.title.bisq.market:Bisq market: ${pairUpperCase}`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.market:See price history, current buy/sell offers, and latest trades for the ${pairUpperCase} market on Bisq.`);
|
||||
|
||||
return {
|
||||
pair: pairUpperCase,
|
||||
|
||||
@@ -26,6 +26,7 @@ export class BisqStatsComponent implements OnInit {
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.stats:See high-level stats on the BSQ economy: supply metrics, number of addresses, BSQ price, market cap, and more.`);
|
||||
this.stateService.bsqPrice$
|
||||
.subscribe((bsqPrice) => {
|
||||
this.price = bsqPrice;
|
||||
|
||||
@@ -48,6 +48,7 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
|
||||
document.body.scrollTo(0, 0);
|
||||
this.txId = params.get('id') || '';
|
||||
this.seoService.setTitle($localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.transaction:See inputs, outputs, transaction type, burnt amount, and more for transaction with txid ${this.txId}:INTERPOLATION:.`);
|
||||
if (history.state.data) {
|
||||
return of(history.state.data);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ export class BisqTransactionsComponent implements OnInit, OnDestroy {
|
||||
ngOnInit(): void {
|
||||
this.websocketService.want(['blocks']);
|
||||
this.seoService.setTitle($localize`:@@add4cd82e3e38a3110fe67b3c7df56e9602644ee:Transactions`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bisq.transactions:See recent BSQ transactions: amount, txid, associated Bitcoin block, transaction type, and more.`);
|
||||
|
||||
this.radioGroupForm = this.formBuilder.group({
|
||||
txTypes: [this.txTypesDefaultChecked],
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
<span style="margin-left: auto; margin-right: -20px; margin-bottom: -20px">®</span>
|
||||
<img class="logo" src="/resources/mempool-logo-bigger.png" />
|
||||
<div class="version">
|
||||
v{{ packetJsonVersion }} [<a href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]
|
||||
<span>v{{ packetJsonVersion }} [<a href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]</span>
|
||||
<span *ngIf="stateService.env.GIT_COMMIT_HASH_MEMPOOL_SPACE">[{{ stateService.env.GIT_COMMIT_HASH_MEMPOOL_SPACE }}]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="about-text">
|
||||
<h5><ng-container i18n="about.about-the-project">The Mempool Open Source Project</ng-container><ng-template [ngIf]="locale.substr(0, 2) === 'en'"> ™</ng-template></h5>
|
||||
<h5><ng-container i18n="about.about-the-project">The Mempool Open Source Project</ng-container><ng-template [ngIf]="locale.substr(0, 2) === 'en'"> ®</ng-template></h5>
|
||||
<p i18n>Our mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, completely self-hosted without any trusted third-parties.</p>
|
||||
</div>
|
||||
|
||||
@@ -242,7 +243,7 @@
|
||||
<img class="image" src="/resources/profile/ronindojo.png" />
|
||||
<span>RoninDojo</span>
|
||||
</a>
|
||||
<a href="https://github.com/runcitadel/core" target="_blank" title="Citadel">
|
||||
<a href="https://github.com/runcitadel" target="_blank" title="Citadel">
|
||||
<img class="image" src="/resources/profile/runcitadel.svg" />
|
||||
<span>Citadel</span>
|
||||
</a>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
.intro {
|
||||
margin: 25px auto 30px;
|
||||
margin-top: 25px;
|
||||
width: 250px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -43,6 +43,7 @@ export class AboutComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.backendInfo$ = this.stateService.backendInfo$;
|
||||
this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project®\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`);
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.profiles$ = this.apiService.getAboutPageProfiles$().pipe(
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="fee-graph" *ngIf="tx && estimate">
|
||||
<div class="column">
|
||||
<ng-container *ngFor="let bar of bars">
|
||||
<div class="bar {{ bar.class }}" [class.active]="bar.active" [style]="bar.style" (click)="onClick($event, bar);">
|
||||
<div class="fill"></div>
|
||||
<div class="line">
|
||||
<p class="fee-rate">
|
||||
<span class="label">{{ bar.label }}</span>
|
||||
<span class="rate">
|
||||
<app-fee-rate [fee]="bar.rate"></app-fee-rate>
|
||||
</span>
|
||||
</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>
|
||||
<div class="spacer"></div>
|
||||
<div class="spacer"></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,157 @@
|
||||
.fee-graph {
|
||||
height: 100%;
|
||||
min-width: 120px;
|
||||
width: 120px;
|
||||
max-height: 90vh;
|
||||
margin-left: 4em;
|
||||
margin-right: 1.5em;
|
||||
padding-bottom: 63px;
|
||||
|
||||
.column {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: #181b2d;
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
opacity: 0.75;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fee {
|
||||
font-size: 0.9em;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
flex-grow: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: -4.5em;
|
||||
border-top: dashed white 1.5px;
|
||||
|
||||
.fee-rate {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0.2em;
|
||||
font-size: 0.8em;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
|
||||
.label {
|
||||
margin-right: .2em;
|
||||
}
|
||||
|
||||
.rate .symbol {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.tx {
|
||||
.fill {
|
||||
background: #3bcc49;
|
||||
}
|
||||
.line {
|
||||
.fee-rate {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.fee {
|
||||
position: absolute;
|
||||
opacity: 1;
|
||||
z-index: 11;
|
||||
}
|
||||
}
|
||||
|
||||
&.target {
|
||||
.fill {
|
||||
background: #653b9c;
|
||||
}
|
||||
.fee {
|
||||
position: absolute;
|
||||
opacity: 1;
|
||||
z-index: 11;
|
||||
}
|
||||
.line .fee-rate {
|
||||
bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.max {
|
||||
cursor: pointer;
|
||||
.line .fee-rate {
|
||||
.label {
|
||||
opacity: 0;
|
||||
}
|
||||
bottom: 2px;
|
||||
}
|
||||
&.active, &:hover {
|
||||
.fill {
|
||||
background: #105fb0;
|
||||
}
|
||||
.line {
|
||||
.fee-rate .label {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.fill {
|
||||
z-index: 10;
|
||||
}
|
||||
.line {
|
||||
z-index: 11;
|
||||
}
|
||||
.fee {
|
||||
opacity: 1;
|
||||
z-index: 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > .bar:not(:hover) {
|
||||
&.target, &.max {
|
||||
.fee {
|
||||
opacity: 0;
|
||||
}
|
||||
.line .fee-rate .label {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
&.max {
|
||||
.fill {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Component, OnInit, Input, Output, OnChanges, EventEmitter, HostListener, Inject, LOCALE_ID } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||
import { Router } from '@angular/router';
|
||||
import { ReplaySubject, merge, Subscription, of } from 'rxjs';
|
||||
import { tap, switchMap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { AccelerationEstimate, RateOption } from './accelerate-preview.component';
|
||||
|
||||
interface GraphBar {
|
||||
rate: number;
|
||||
style: any;
|
||||
class: 'tx' | 'target' | 'max';
|
||||
label: string;
|
||||
active?: boolean;
|
||||
rateIndex?: number;
|
||||
fee?: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerate-fee-graph',
|
||||
templateUrl: './accelerate-fee-graph.component.html',
|
||||
styleUrls: ['./accelerate-fee-graph.component.scss'],
|
||||
})
|
||||
export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
|
||||
@Input() tx: Transaction;
|
||||
@Input() estimate: AccelerationEstimate;
|
||||
@Input() maxRateOptions: RateOption[] = [];
|
||||
@Input() maxRateIndex: number = 0;
|
||||
@Output() setUserBid = new EventEmitter<{ fee: number, index: number }>();
|
||||
|
||||
bars: GraphBar[] = [];
|
||||
tooltipPosition = { x: 0, y: 0 };
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initGraph();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.initGraph();
|
||||
}
|
||||
|
||||
initGraph(): void {
|
||||
if (!this.tx || !this.estimate) {
|
||||
return;
|
||||
}
|
||||
const maxRate = Math.max(...this.maxRateOptions.map(option => option.rate));
|
||||
const baseRate = this.estimate.txSummary.effectiveFee / this.estimate.txSummary.effectiveVsize;
|
||||
const baseHeight = baseRate / maxRate;
|
||||
const bars: GraphBar[] = this.maxRateOptions.slice().reverse().map(option => {
|
||||
return {
|
||||
rate: option.rate,
|
||||
style: this.getStyle(option.rate, maxRate, baseHeight),
|
||||
class: 'max',
|
||||
label: 'maximum',
|
||||
active: option.index === this.maxRateIndex,
|
||||
rateIndex: option.index,
|
||||
fee: option.fee,
|
||||
}
|
||||
});
|
||||
bars.push({
|
||||
rate: this.estimate.targetFeeRate,
|
||||
style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
|
||||
class: 'target',
|
||||
label: 'next block',
|
||||
fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
|
||||
});
|
||||
bars.push({
|
||||
rate: baseRate,
|
||||
style: this.getStyle(baseRate, maxRate, 0),
|
||||
class: 'tx',
|
||||
label: '',
|
||||
fee: this.estimate.txSummary.effectiveFee,
|
||||
});
|
||||
this.bars = bars;
|
||||
}
|
||||
|
||||
getStyle(rate, maxRate, base) {
|
||||
const top = (rate / maxRate);
|
||||
return {
|
||||
height: `${(top - base) * 100}%`,
|
||||
bottom: base ? `${base * 100}%` : '0',
|
||||
}
|
||||
}
|
||||
|
||||
onClick(event, bar): void {
|
||||
if (bar.rateIndex != null) {
|
||||
this.setUserBid.emit({ fee: bar.fee, index: bar.rateIndex });
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('pointermove', ['$event'])
|
||||
onPointerMove(event) {
|
||||
this.tooltipPosition = { x: event.offsetX, y: event.offsetY };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<div class="row" *ngIf="showSuccess">
|
||||
<div class="col" id="successAlert">
|
||||
<div class="alert alert-success">
|
||||
Transaction has now been submitted to mining pools for acceleration. You can track the progress <a class="alert-link" routerLink="/services/accelerator/history">here</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" *ngIf="error">
|
||||
<div class="col" id="mempoolError">
|
||||
<app-mempool-error [error]="error"></app-mempool-error>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accelerate-cols">
|
||||
<ng-container *ngIf="!isMobile">
|
||||
<app-accelerate-fee-graph
|
||||
[tx]="tx"
|
||||
[estimate]="estimate"
|
||||
[maxRateOptions]="maxRateOptions"
|
||||
[maxRateIndex]="selectFeeRateIndex"
|
||||
(setUserBid)="setUserBid($event)"
|
||||
></app-accelerate-fee-graph>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="estimate">
|
||||
<div [class]="{estimateDisabled: error}">
|
||||
<h5>Your transaction</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
|
||||
Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor{{ estimate.txSummary.ancestorCount > 2 ? 's' : ''}}.
|
||||
</small>
|
||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
||||
<tbody>
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
Virtual size
|
||||
</td>
|
||||
<td class="units" [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info">
|
||||
<i><small>Size in vbytes of this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="item">
|
||||
In-band fees
|
||||
</td>
|
||||
<td class="units">
|
||||
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info">
|
||||
<i><small>Fees already paid by this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<h5>How much more are you willing to pay?</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small class="form-text text-muted mb-2">
|
||||
Choose the maximum extra transaction fee you're willing to pay to get into the next block.<br>
|
||||
If the estimated next block rate rises beyond this limit, we will automatically cancel your acceleration request.
|
||||
</small>
|
||||
<div class="form-group">
|
||||
<div class="fee-card">
|
||||
<div class="d-flex mb-0">
|
||||
<ng-container *ngFor="let option of maxRateOptions">
|
||||
<button type="button" class="btn btn-primary flex-grow-1 btn-border btn-sm feerate" [class]="{active: selectFeeRateIndex === option.index}" (click)="setUserBid(option)">
|
||||
<span class="fee">{{ option.fee | number }} <span class="symbol" i18n="shared.sats|sats">sats</span></span>
|
||||
<span class="rate">~ <app-fee-rate [fee]="option.rate" rounding="1.0-0"></app-fee-rate></span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Acceleration summary</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="table-toggle btn-group btn-group-toggle">
|
||||
<div class="btn btn-primary btn-sm" [class.active]="showTable === 'estimated'" (click)="showTable = 'estimated'">
|
||||
<span>Estimated cost</span>
|
||||
</div>
|
||||
<div class="btn btn-primary btn-sm" [class.active]="showTable === 'maximum'" (click)="showTable = 'maximum'">
|
||||
<span>Maximum cost</span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
||||
<tbody>
|
||||
<!-- ESTIMATED FEE -->
|
||||
<ng-container *ngIf="showTable === 'estimated'">
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
Next block market rate
|
||||
</td>
|
||||
<td class="amt" style="font-size: 20px">
|
||||
{{ estimate.targetFeeRate | number : '1.0-0' }}
|
||||
</td>
|
||||
<td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info">
|
||||
<i><small>Estimated extra fee required</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<!-- USER MAX BID -->
|
||||
<ng-container *ngIf="showTable === 'maximum'">
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
Your maximum
|
||||
</td>
|
||||
<td class="amt" style="width: 45%; font-size: 20px">
|
||||
~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }}
|
||||
</td>
|
||||
<td class="units"><span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info">
|
||||
<i><small>The maximum extra transaction fee you could pay</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
<span>
|
||||
{{ userBid | number }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="userBid"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
<!-- MEMPOOL BASE FEE -->
|
||||
<tr>
|
||||
<td class="item">
|
||||
Mempool Accelerator™ fees
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info">
|
||||
<i><small>mempool.space fee</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
+{{ estimate.mempoolBaseFee | number }}
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
|
||||
<td class="info">
|
||||
<i><small>Transaction vsize fee</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
+{{ estimate.vsizeFee | number }}
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.vsizeFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- NEXT BLOCK ESTIMATE -->
|
||||
<ng-container *ngIf="showTable === 'estimated'">
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
||||
</td>
|
||||
<td class="amt">
|
||||
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
||||
{{ estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee | number }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info">
|
||||
<i><small>If your tx is accelerated to </small><small>{{ estimate.targetFeeRate | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
<!-- MAX COST -->
|
||||
<ng-container *ngIf="showTable === 'maximum'">
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
<b style="background-color: #105fb0;" class="p-1 pl-0">Maximum acceleration cost</b>
|
||||
</td>
|
||||
<td class="amt">
|
||||
<span style="background-color: #105fb0" class="p-1 pl-0">
|
||||
{{ maxCost | number }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info">
|
||||
<i><small>If your tx is accelerated to </small><small>~{{ ((estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize) | number : '1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
<!-- USER BALANCE -->
|
||||
<ng-container *ngIf="estimate.userBalance < maxCost">
|
||||
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||
<td class="item">
|
||||
Available balance
|
||||
</td>
|
||||
<td class="amt">
|
||||
{{ estimate.userBalance | number }}
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats|sats">sats</span>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="estimate.userBalance" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3" *ngIf="isLoggedIn()">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -0,0 +1,88 @@
|
||||
.fee-card {
|
||||
padding: 15px;
|
||||
background-color: #1d1f31;
|
||||
|
||||
.feerate {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.fee {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.rate {
|
||||
font-size: 0.9em;
|
||||
.symbol {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-border {
|
||||
border: solid 1px black;
|
||||
background-color: #0c4a87;
|
||||
}
|
||||
|
||||
.feerate.active {
|
||||
background-color: #105fb0 !important;
|
||||
opacity: 1;
|
||||
border: 1px solid white !important;
|
||||
}
|
||||
|
||||
.estimateDisabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.table-toggle {
|
||||
width: 100%;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.table-accelerator {
|
||||
tr {
|
||||
text-wrap: wrap;
|
||||
|
||||
td {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
&.group-first {
|
||||
td {
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
&.group-last {
|
||||
td {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
td {
|
||||
&:first-child {
|
||||
width: 100vw;
|
||||
}
|
||||
&.info {
|
||||
color: #6c757d;
|
||||
}
|
||||
&.amt {
|
||||
text-align: right;
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
&.units {
|
||||
padding-left: 0.2em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.accelerate-cols {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
margin-top: 1em;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener } from '@angular/core';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { Subscription, catchError, of, tap } from 'rxjs';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
import { nextRoundNumber } from '../../shared/common.utils';
|
||||
|
||||
export type AccelerationEstimate = {
|
||||
txSummary: TxSummary;
|
||||
nextBlockFee: number;
|
||||
targetFeeRate: number;
|
||||
userBalance: number;
|
||||
enoughBalance: boolean;
|
||||
cost: number;
|
||||
mempoolBaseFee: number;
|
||||
vsizeFee: number;
|
||||
}
|
||||
export type TxSummary = {
|
||||
txid: string; // txid of the current transaction
|
||||
effectiveVsize: number; // Total vsize of the dependency tree
|
||||
effectiveFee: number; // Total fee of the dependency tree in sats
|
||||
ancestorCount: number; // Number of ancestors
|
||||
}
|
||||
|
||||
export interface RateOption {
|
||||
fee: number;
|
||||
rate: number;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const MIN_BID_RATIO = 1;
|
||||
export const DEFAULT_BID_RATIO = 2;
|
||||
export const MAX_BID_RATIO = 4;
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerate-preview',
|
||||
templateUrl: 'accelerate-preview.component.html',
|
||||
styleUrls: ['accelerate-preview.component.scss']
|
||||
})
|
||||
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() tx: Transaction | undefined;
|
||||
@Input() scrollEvent: boolean;
|
||||
|
||||
math = Math;
|
||||
error = '';
|
||||
showSuccess = false;
|
||||
estimateSubscription: Subscription;
|
||||
accelerationSubscription: Subscription;
|
||||
estimate: any;
|
||||
hasAncestors: boolean = false;
|
||||
minExtraCost = 0;
|
||||
minBidAllowed = 0;
|
||||
maxBidAllowed = 0;
|
||||
defaultBid = 0;
|
||||
maxCost = 0;
|
||||
userBid = 0;
|
||||
selectFeeRateIndex = 1;
|
||||
showTable: 'estimated' | 'maximum' = 'maximum';
|
||||
isMobile: boolean = window.innerWidth <= 767.98;
|
||||
|
||||
maxRateOptions: RateOption[] = [];
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private storageService: StorageService
|
||||
) { }
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.estimateSubscription) {
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.scrollEvent) {
|
||||
this.scrollToPreview('acceleratePreviewAnchor', 'center');
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.estimateSubscription = this.apiService.estimate$(this.tx.txid).pipe(
|
||||
tap((response) => {
|
||||
if (response.status === 204) {
|
||||
this.estimate = undefined;
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
} else {
|
||||
this.estimate = response.body;
|
||||
if (!this.estimate) {
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.estimate.userBalance <= 0) {
|
||||
if (this.isLoggedIn()) {
|
||||
this.error = `not_enough_balance`;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
}
|
||||
}
|
||||
|
||||
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
|
||||
|
||||
// Make min extra fee at least 50% of the current tx fee
|
||||
this.minExtraCost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee));
|
||||
|
||||
this.maxRateOptions = [1, 2, 4].map((multiplier, index) => {
|
||||
return {
|
||||
fee: this.minExtraCost * multiplier,
|
||||
rate: (this.estimate.txSummary.effectiveFee + (this.minExtraCost * multiplier)) / this.estimate.txSummary.effectiveVsize,
|
||||
index,
|
||||
};
|
||||
});
|
||||
|
||||
this.minBidAllowed = this.minExtraCost * MIN_BID_RATIO;
|
||||
this.defaultBid = this.minExtraCost * DEFAULT_BID_RATIO;
|
||||
this.maxBidAllowed = this.minExtraCost * MAX_BID_RATIO;
|
||||
|
||||
this.userBid = this.defaultBid;
|
||||
if (this.userBid < this.minBidAllowed) {
|
||||
this.userBid = this.minBidAllowed;
|
||||
} else if (this.userBid > this.maxBidAllowed) {
|
||||
this.userBid = this.maxBidAllowed;
|
||||
}
|
||||
this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
||||
|
||||
if (!this.error) {
|
||||
this.scrollToPreview('acceleratePreviewAnchor', 'center');
|
||||
}
|
||||
}
|
||||
}),
|
||||
catchError((response) => {
|
||||
this.estimate = undefined;
|
||||
this.error = response.error;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
return of(null);
|
||||
})
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* User changed his bid
|
||||
*/
|
||||
setUserBid({ fee, index }: { fee: number, index: number}) {
|
||||
if (this.estimate) {
|
||||
this.selectFeeRateIndex = index;
|
||||
this.userBid = Math.max(0, fee);
|
||||
this.maxCost = this.userBid + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to element id with or without setTimeout
|
||||
*/
|
||||
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) {
|
||||
setTimeout(() => {
|
||||
this.scrollToPreview(id, position);
|
||||
}, 100);
|
||||
}
|
||||
scrollToPreview(id: string, position: ScrollLogicalPosition) {
|
||||
const acceleratePreviewAnchor = document.getElementById(id);
|
||||
if (acceleratePreviewAnchor) {
|
||||
acceleratePreviewAnchor.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: position,
|
||||
block: position,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send acceleration request
|
||||
*/
|
||||
accelerate() {
|
||||
if (this.accelerationSubscription) {
|
||||
this.accelerationSubscription.unsubscribe();
|
||||
}
|
||||
this.accelerationSubscription = this.apiService.accelerate$(
|
||||
this.tx.txid,
|
||||
this.userBid
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.showSuccess = true;
|
||||
this.scrollToPreviewWithTimeout('successAlert', 'center');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
},
|
||||
error: (response) => {
|
||||
this.error = response.error;
|
||||
this.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
const auth = this.storageService.getAuth();
|
||||
return auth !== null;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
this.isMobile = window.innerWidth <= 767.98;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { AudioService } from '../../services/audio.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { of, merge, Subscription, Observable } from 'rxjs';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { AddressInformation } from '../../interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
@@ -68,6 +69,7 @@ export class AddressPreviewComponent implements OnInit, OnDestroy {
|
||||
this.addressString = this.addressString.toLowerCase();
|
||||
}
|
||||
this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`);
|
||||
|
||||
return (this.addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/)
|
||||
? this.electrsApiService.getPubKeyAddress$(this.addressString)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AudioService } from '../../services/audio.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { of, merge, Subscription, Observable } from 'rxjs';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { AddressInformation } from '../../interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
@@ -76,6 +77,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
this.addressString = this.addressString.toLowerCase();
|
||||
}
|
||||
this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`);
|
||||
|
||||
return merge(
|
||||
of(true),
|
||||
@@ -172,6 +174,11 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
this.addTransaction(tx);
|
||||
});
|
||||
|
||||
this.stateService.mempoolRemovedTransactions$
|
||||
.subscribe(tx => {
|
||||
this.removeTransaction(tx);
|
||||
});
|
||||
|
||||
this.stateService.blockTransactions$
|
||||
.subscribe((transaction) => {
|
||||
const tx = this.transactions.find((t) => t.txid === transaction.txid);
|
||||
@@ -220,6 +227,30 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
return true;
|
||||
}
|
||||
|
||||
removeTransaction(transaction: Transaction): boolean {
|
||||
const index = this.transactions.findIndex(((tx) => tx.txid === transaction.txid));
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.transactions.splice(index, 1);
|
||||
this.transactions = this.transactions.slice();
|
||||
this.txCount--;
|
||||
|
||||
transaction.vin.forEach((vin) => {
|
||||
if (vin?.prevout?.scriptpubkey_address === this.address.address) {
|
||||
this.sent -= vin.prevout.value;
|
||||
}
|
||||
});
|
||||
transaction.vout.forEach((vout) => {
|
||||
if (vout?.scriptpubkey_address === this.address.address) {
|
||||
this.received -= vout.value;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (this.isLoadingTransactions || !this.totalConfirmedTxCount || this.loadedConfirmedTxCount >= this.totalConfirmedTxCount) {
|
||||
return;
|
||||
|
||||
@@ -40,6 +40,7 @@ export class AssetsNavComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.liquid.assets:Explore all the assets issued on the Liquid network like L-BTC, L-CAD, USDT, and more.`);
|
||||
this.typeaheadSearchFn = this.typeaheadSearch;
|
||||
|
||||
this.searchForm = this.formBuilder.group({
|
||||
|
||||
@@ -64,6 +64,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@ed8e33059967f554ff06b4f5b6049c465b92d9b3:Block Fee Rates`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fee-rates:See Bitcoin feerates visualized over time, including minimum and maximum feerates per block along with feerates at various percentiles.`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
|
||||
@@ -65,6 +65,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@6c453b11fd7bd159ae30bc381f367bc736d86909:Block Fees`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fees:See the average mining fees earned per Bitcoin block visualized in BTC and USD over time.`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('1m');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
@@ -192,7 +193,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
||||
{
|
||||
name: 'Fees ' + this.currency,
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
icon: 'roundRect',
|
||||
|
||||
@@ -61,6 +61,7 @@ export class BlockHealthGraphComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@d7d5fcf50179ad70c938491c517efb82de2c8146:Block Health`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-health:See Bitcoin block health visualized over time. Block health is a measure of how many expected transactions were included in an actual mined block. Expected transactions are determined using Mempool's re-implementation of Bitcoin Core's transaction selection algorithm.`);
|
||||
this.miningWindowPreference = '24h';//this.miningService.getDefaultTimespan('24h');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
|
||||
@@ -63,6 +63,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@8ba8fe810458280a83df7fdf4c614dfc1a826445:Block Rewards`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-rewards:See Bitcoin block rewards in BTC and USD visualized over time. Block rewards are the total funds miners earn from the block subsidy and fees.`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('3m');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
@@ -191,7 +192,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
||||
{
|
||||
name: 'Rewards ' + this.currency,
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
icon: 'roundRect',
|
||||
|
||||
@@ -60,6 +60,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
||||
let firstRun = true;
|
||||
|
||||
this.seoService.setTitle($localize`:@@56fa1cd221491b6478998679cba2dc8d55ba330d:Block Sizes and Weights`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-sizes:See Bitcoin block sizes (MB) and block weights (weight units) visualized over time.`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SeoService } from '../../services/seo.service';
|
||||
import { OpenGraphService } from '../../services/opengraph.service';
|
||||
import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||
|
||||
@Component({
|
||||
@@ -97,6 +98,11 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
|
||||
this.blockHeight = block.height;
|
||||
|
||||
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
||||
if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) {
|
||||
this.seoService.setDescription($localize`:@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||
} else {
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||
}
|
||||
this.isLoadingBlock = false;
|
||||
this.setBlockSubsidy();
|
||||
if (block?.extras?.reward !== undefined) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||
import { detectWebGL } from '../../shared/graphs.utils';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { PriceService, Price } from '../../services/price.service';
|
||||
import { CacheService } from '../../services/cache.service';
|
||||
|
||||
@@ -165,7 +166,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.page = 1;
|
||||
this.error = undefined;
|
||||
this.fees = undefined;
|
||||
this.stateService.markBlock$.next({});
|
||||
|
||||
if (history.state.data && history.state.data.blockHeight) {
|
||||
this.blockHeight = history.state.data.blockHeight;
|
||||
@@ -175,6 +175,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
let isBlockHeight = false;
|
||||
if (/^[0-9]+$/.test(blockHash)) {
|
||||
isBlockHeight = true;
|
||||
this.stateService.markBlock$.next({ blockHeight: parseInt(blockHash, 10)});
|
||||
} else {
|
||||
this.blockHash = blockHash;
|
||||
}
|
||||
@@ -201,6 +202,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.location.replaceState(
|
||||
this.router.createUrlTree([(this.network ? '/' + this.network : '') + '/block/', hash]).toString()
|
||||
);
|
||||
this.seoService.updateCanonical(this.location.path());
|
||||
return this.apiService.getBlock$(hash).pipe(
|
||||
catchError((err) => {
|
||||
this.error = err;
|
||||
@@ -261,6 +263,11 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.setNextAndPreviousBlockLink();
|
||||
|
||||
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
||||
if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) {
|
||||
this.seoService.setDescription($localize`:@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||
} else {
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||
}
|
||||
this.isLoadingBlock = false;
|
||||
this.setBlockSubsidy();
|
||||
if (block?.extras?.reward !== undefined) {
|
||||
@@ -325,7 +332,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
]);
|
||||
})
|
||||
)
|
||||
.subscribe(([transactions, blockAudit]) => {
|
||||
.subscribe(([transactions, blockAudit]) => {
|
||||
if (transactions) {
|
||||
this.strippedTransactions = transactions;
|
||||
} else {
|
||||
@@ -680,7 +687,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.setAuditAvailable(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isAuditAvailableFromBlockHeight(blockHeight: number): boolean {
|
||||
if (!this.auditSupported) {
|
||||
return false;
|
||||
@@ -729,4 +736,4 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.block.canonical = block.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="text-center" class="blockchain-wrapper" [class.time-ltr]="timeLtr" [class.ltr-transition]="ltrTransitionEnabled" #container>
|
||||
<div class="position-container" [ngClass]="network ? network : ''" [style.--divider-offset]="dividerOffset + 'px'" [style.--mempool-offset]="mempoolOffset + 'px'">
|
||||
<div #positionContainer class="position-container" [ngClass]="network ? network : ''" [style]="positionStyle">
|
||||
<span>
|
||||
<div class="blocks-wrapper">
|
||||
<div class="scroll-spacer" *ngIf="minScrollWidth" [style.left]="minScrollWidth + 'px'"></div>
|
||||
|
||||
@@ -26,15 +26,7 @@
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 75px;
|
||||
--divider-offset: 50vw;
|
||||
--mempool-offset: 0px;
|
||||
transform: translateX(calc(var(--divider-offset) + var(--mempool-offset)));
|
||||
}
|
||||
|
||||
.blockchain-wrapper.time-ltr {
|
||||
.position-container {
|
||||
transform: translateX(calc(100vw - var(--divider-offset) - var(--mempool-offset)));
|
||||
}
|
||||
transform: translateX(1280px);
|
||||
}
|
||||
|
||||
.black-background {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, HostListener, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { firstValueFrom, Subscription } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
||||
@@ -8,12 +8,13 @@ import { StateService } from '../../services/state.service';
|
||||
styleUrls: ['./blockchain.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
export class BlockchainComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() pages: any[] = [];
|
||||
@Input() pageIndex: number;
|
||||
@Input() blocksPerPage: number = 8;
|
||||
@Input() minScrollWidth: number = 0;
|
||||
@Input() scrollableMempool: boolean = false;
|
||||
@Input() containerWidth: number;
|
||||
|
||||
@Output() mempoolOffsetChange: EventEmitter<number> = new EventEmitter();
|
||||
|
||||
@@ -26,8 +27,11 @@ export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
loadingTip: boolean = true;
|
||||
connected: boolean = true;
|
||||
|
||||
dividerOffset: number = 0;
|
||||
mempoolOffset: number = 0;
|
||||
dividerOffset: number | null = null;
|
||||
mempoolOffset: number | null = null;
|
||||
positionStyle = {
|
||||
transform: "translateX(1280px)",
|
||||
};
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
@@ -39,6 +43,7 @@ export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
this.network = this.stateService.network;
|
||||
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||
this.timeLtr = !!ltr;
|
||||
this.updateStyle();
|
||||
});
|
||||
this.connectionStateSubscription = this.stateService.connectionState$.subscribe(state => {
|
||||
this.connected = (state === 2);
|
||||
@@ -62,44 +67,68 @@ export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
const prevOffset = this.mempoolOffset;
|
||||
this.mempoolOffset = 0;
|
||||
this.mempoolOffsetChange.emit(0);
|
||||
this.updateStyle();
|
||||
setTimeout(() => {
|
||||
this.ltrTransitionEnabled = true;
|
||||
this.flipping = true;
|
||||
this.stateService.timeLtr.next(!this.timeLtr);
|
||||
this.cd.markForCheck();
|
||||
setTimeout(() => {
|
||||
this.ltrTransitionEnabled = false;
|
||||
this.flipping = false;
|
||||
this.mempoolOffset = prevOffset;
|
||||
this.mempoolOffsetChange.emit(this.mempoolOffset);
|
||||
this.mempoolOffsetChange.emit((this.mempoolOffset || 0));
|
||||
this.updateStyle();
|
||||
this.cd.markForCheck();
|
||||
}, 1000);
|
||||
}, 0);
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
onMempoolWidthChange(width): void {
|
||||
if (this.flipping) {
|
||||
return;
|
||||
}
|
||||
this.mempoolOffset = Math.max(0, width - this.dividerOffset);
|
||||
this.cd.markForCheck();
|
||||
this.mempoolOffset = Math.max(0, width - (this.dividerOffset || 0));
|
||||
this.updateStyle();
|
||||
this.mempoolOffsetChange.emit(this.mempoolOffset);
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
updateStyle(): void {
|
||||
if (this.dividerOffset == null || this.mempoolOffset == null) {
|
||||
return;
|
||||
}
|
||||
const oldTransform = this.positionStyle.transform;
|
||||
this.positionStyle = this.timeLtr ? {
|
||||
transform: `translateX(calc(100vw - ${this.dividerOffset + this.mempoolOffset}px)`,
|
||||
} : {
|
||||
transform: `translateX(${this.dividerOffset + this.mempoolOffset}px)`,
|
||||
};
|
||||
if (oldTransform !== this.positionStyle.transform) {
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.containerWidth) {
|
||||
this.onResize();
|
||||
}
|
||||
}
|
||||
|
||||
onResize(): void {
|
||||
if (window.innerWidth >= 768) {
|
||||
const width = this.containerWidth || window.innerWidth;
|
||||
if (width >= 768) {
|
||||
if (this.stateService.isLiquid()) {
|
||||
this.dividerOffset = 420;
|
||||
} else {
|
||||
this.dividerOffset = window.innerWidth * 0.5;
|
||||
this.dividerOffset = width * 0.5;
|
||||
}
|
||||
} else {
|
||||
if (this.stateService.isLiquid()) {
|
||||
this.dividerOffset = window.innerWidth * 0.5;
|
||||
this.dividerOffset = width * 0.5;
|
||||
} else {
|
||||
this.dividerOffset = window.innerWidth * 0.95;
|
||||
this.dividerOffset = width * 0.95;
|
||||
}
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
this.updateStyle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<app-indexing-progress *ngIf="!widget"></app-indexing-progress>
|
||||
|
||||
<div class="container-xl" style="min-height: 335px" [ngClass]="{'widget': widget, 'full-height': !widget, 'legacy': !indexingAvailable}">
|
||||
<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>
|
||||
|
||||
@@ -9,28 +9,28 @@
|
||||
<div style="min-height: 295px">
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<th class="height text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}" i18n="latest-blocks.height">Height</th>
|
||||
<th *ngIf="indexingAvailable" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}" i18n="mining.pool-name"
|
||||
<th class="height text-left" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}" i18n="latest-blocks.height">Height</th>
|
||||
<th *ngIf="isMempoolModule" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}" i18n="mining.pool-name"
|
||||
i18n-ngbTooltip="mining.pool-name" ngbTooltip="Pool" placement="bottom" #miningpool [disableTooltip]="!isEllipsisActive(miningpool)">Pool</th>
|
||||
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Timestamp</th>
|
||||
<th *ngIf="auditAvailable" class="health text-right" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget" [class]="isMempoolModule ? '' : 'legacy'">Timestamp</th>
|
||||
<th *ngIf="auditAvailable" class="health text-right" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}"
|
||||
i18n-ngbTooltip="latest-blocks.health" ngbTooltip="Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Health</th>
|
||||
<th *ngIf="indexingAvailable" class="reward text-right" i18n="latest-blocks.reward" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
<th *ngIf="isMempoolModule" class="reward text-right" i18n="latest-blocks.reward" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}"
|
||||
i18n-ngbTooltip="latest-blocks.reward" ngbTooltip="Reward" placement="bottom" #reward [disableTooltip]="!isEllipsisActive(reward)">Reward</th>
|
||||
<th *ngIf="indexingAvailable && !widget" class="fees text-right" i18n="latest-blocks.fees" [class]="indexingAvailable ? '' : 'legacy'">Fees</th>
|
||||
<th *ngIf="auditAvailable && !widget" class="fee-delta" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"></th>
|
||||
<th *ngIf="indexingAvailable" class="txs text-right" i18n="dashboard.txs" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
<th *ngIf="isMempoolModule && !auditAvailable || isMempoolModule && !widget" class="fees text-right" i18n="latest-blocks.fees" [class]="isMempoolModule ? '' : 'legacy'">Fees</th>
|
||||
<th *ngIf="auditAvailable && !widget" class="fee-delta" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}"></th>
|
||||
<th *ngIf="isMempoolModule" class="txs text-right" i18n="dashboard.txs" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}"
|
||||
i18n-ngbTooltip="dashboard.txs" ngbTooltip="TXs" placement="bottom" #txs [disableTooltip]="!isEllipsisActive(txs)">TXs</th>
|
||||
<th *ngIf="!indexingAvailable" class="txs text-right" i18n="dashboard.txs" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">Transactions</th>
|
||||
<th class="size" i18n="latest-blocks.size" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Size</th>
|
||||
<th *ngIf="!isMempoolModule" class="txs text-right" i18n="dashboard.txs" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">Transactions</th>
|
||||
<th class="size" i18n="latest-blocks.size" *ngIf="!widget" [class]="isMempoolModule ? '' : 'legacy'">Size</th>
|
||||
</thead>
|
||||
<tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
||||
<td class="height text-left" [class]="widget ? 'widget' : ''">
|
||||
<a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<div class="tooltip-custom">
|
||||
<td *ngIf="isMempoolModule" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<div *ngIf="indexingAvailable" class="tooltip-custom">
|
||||
<a class="clear-link" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]">
|
||||
<img width="22" height="22" src="{{ block.extras.pool['logo'] }}"
|
||||
onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
||||
@@ -38,11 +38,17 @@
|
||||
</a>
|
||||
<span *ngIf="!widget" class="tooltiptext badge badge-secondary scriptmessage">{{ block.extras.coinbaseRaw | hex2ascii }}</span>
|
||||
</div>
|
||||
<div *ngIf="!indexingAvailable" class="tooltip-custom">
|
||||
<img width="22" height="22" src="{{ block.extras.pool['logo'] }}"
|
||||
onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
||||
<span class="pool-name">{{ block.extras.pool.name }}</span>
|
||||
<span *ngIf="!widget" class="tooltiptext badge badge-secondary scriptmessage">{{ block.extras.coinbaseRaw | hex2ascii }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="timestamp" *ngIf="!widget" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td class="timestamp" *ngIf="!widget" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
</td>
|
||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<a
|
||||
*ngIf="block?.extras?.matchRate != null; else nullHealth"
|
||||
class="health-badge badge"
|
||||
@@ -56,21 +62,21 @@
|
||||
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td *ngIf="isMempoolModule" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<app-amount [satoshis]="block.extras.reward" [noFiat]="true" digitsInfo="1.2-2"></app-amount>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable && !widget" class="fees text-right" [class]="indexingAvailable ? '' : 'legacy'">
|
||||
<td *ngIf="isMempoolModule && !auditAvailable || isMempoolModule && !widget" class="fees text-right" [class]="isMempoolModule ? '' : 'legacy'">
|
||||
<app-amount [satoshis]="block.extras.totalFees" [noFiat]="true" digitsInfo="1.2-2"></app-amount>
|
||||
</td>
|
||||
<td *ngIf="auditAvailable" class="fee-delta" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td *ngIf="auditAvailable" class="fee-delta" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<span *ngIf="block.extras.feeDelta" class="difference" [class.positive]="block.extras.feeDelta >= 0" [class.negative]="block.extras.feeDelta < 0">
|
||||
{{ block.extras.feeDelta > 0 ? '+' : '' }}{{ (block.extras.feeDelta * 100) | amountShortener: 2 }}%
|
||||
</span>
|
||||
</td>
|
||||
<td class="txs text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td class="txs text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
{{ block.tx_count | number }}
|
||||
</td>
|
||||
<td class="size" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">
|
||||
<td class="size" *ngIf="!widget" [class]="isMempoolModule ? '' : 'legacy'">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-mempool" role="progressbar"
|
||||
[ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"></div>
|
||||
@@ -82,34 +88,34 @@
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of skeletonLines">
|
||||
<td class="height text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td class="height text-left" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td *ngIf="isMempoolModule" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<span class="skeleton-loader" style="max-width: 125px"></span>
|
||||
</td>
|
||||
<td class="timestamp" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">
|
||||
<td class="timestamp" *ngIf="!widget" [class]="isMempoolModule ? '' : 'legacy'">
|
||||
<span class="skeleton-loader" style="max-width: 150px"></span>
|
||||
</td>
|
||||
<td class="mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">
|
||||
<td class="mined" *ngIf="!widget" [class]="isMempoolModule ? '' : 'legacy'">
|
||||
<span class="skeleton-loader" style="max-width: 125px"></span>
|
||||
</td>
|
||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td *ngIf="isMempoolModule" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable && !widget" class="fees text-right" [class]="indexingAvailable ? '' : 'legacy'">
|
||||
<td *ngIf="isMempoolModule && !widget" class="fees text-right" [class]="isMempoolModule ? '' : 'legacy'">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td *ngIf="auditAvailable && !widget" class="fee-delta" [class]="indexingAvailable ? '' : 'legacy'">
|
||||
<td *ngIf="auditAvailable && !widget" class="fee-delta" [class]="isMempoolModule ? '' : 'legacy'">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="txs text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<td class="txs text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="size" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">
|
||||
<td class="size" *ngIf="!widget" [class]="isMempoolModule ? '' : 'legacy'">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-blocks-list',
|
||||
@@ -17,6 +19,7 @@ export class BlocksList implements OnInit {
|
||||
|
||||
blocks$: Observable<BlockExtended[]> = undefined;
|
||||
|
||||
isMempoolModule = false;
|
||||
indexingAvailable = false;
|
||||
auditAvailable = false;
|
||||
isLoading = true;
|
||||
@@ -35,7 +38,9 @@ export class BlocksList implements OnInit {
|
||||
private websocketService: WebsocketService,
|
||||
public stateService: StateService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private seoService: SeoService,
|
||||
) {
|
||||
this.isMempoolModule = this.stateService.env.BASE_MODULE === 'mempool';
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -50,6 +55,14 @@ export class BlocksList implements OnInit {
|
||||
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
|
||||
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||
|
||||
this.seoService.setTitle($localize`:@@meta.title.blocks-list:Blocks`);
|
||||
if( this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet' ) {
|
||||
this.seoService.setDescription($localize`:@@meta.description.liquid.blocks:See the most recent Liquid${seoDescriptionNetwork(this.stateService.network)} blocks along with basic stats such as block height, block size, and more.`);
|
||||
} else {
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.blocks:See the most recent Bitcoin${seoDescriptionNetwork(this.stateService.network)} blocks along with basic stats such as block height, block reward, block size, and more.`);
|
||||
}
|
||||
|
||||
|
||||
this.blocks$ = combineLatest([
|
||||
this.fromHeightSubject.pipe(
|
||||
switchMap((fromBlockHeight) => {
|
||||
@@ -64,11 +77,10 @@ export class BlocksList implements OnInit {
|
||||
this.lastBlockHeight = Math.max(...blocks.map(o => o.height));
|
||||
}),
|
||||
map(blocks => {
|
||||
if (this.indexingAvailable) {
|
||||
if (this.stateService.env.BASE_MODULE === 'mempool') {
|
||||
for (const block of blocks) {
|
||||
// @ts-ignore: Need to add an extra field for the template
|
||||
block.extras.pool.logo = `/resources/mining-pools/` +
|
||||
block.extras.pool.slug + '.svg';
|
||||
block.extras.pool.logo = `/resources/mining-pools/` + block.extras.pool.slug + '.svg';
|
||||
}
|
||||
}
|
||||
if (this.widget) {
|
||||
@@ -99,7 +111,7 @@ export class BlocksList implements OnInit {
|
||||
}
|
||||
if (blocks[1]) {
|
||||
this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height) + 1;
|
||||
if (this.stateService.env.MINING_DASHBOARD) {
|
||||
if (this.isMempoolModule) {
|
||||
// @ts-ignore: Need to add an extra field for the template
|
||||
blocks[1][0].extras.pool.logo = `/resources/mining-pools/` +
|
||||
blocks[1][0].extras.pool.slug + '.svg';
|
||||
@@ -110,9 +122,11 @@ export class BlocksList implements OnInit {
|
||||
return acc;
|
||||
}, []),
|
||||
switchMap((blocks) => {
|
||||
blocks.forEach(block => {
|
||||
block.extras.feeDelta = block.extras.expectedFees ? (block.extras.totalFees - block.extras.expectedFees) / block.extras.expectedFees : 0;
|
||||
});
|
||||
if (this.isMempoolModule && this.auditAvailable) {
|
||||
blocks.forEach(block => {
|
||||
block.extras.feeDelta = block.extras.expectedFees ? (block.extras.totalFees - block.extras.expectedFees) / block.extras.expectedFees : 0;
|
||||
});
|
||||
}
|
||||
return of(blocks);
|
||||
})
|
||||
);
|
||||
@@ -129,4 +143,4 @@ export class BlocksList implements OnInit {
|
||||
isEllipsisActive(e): boolean {
|
||||
return (e.offsetWidth < e.scrollWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ export class DifficultyComponent implements OnInit {
|
||||
|
||||
@HostListener('pointerdown', ['$event'])
|
||||
onPointerDown(event): void {
|
||||
if (this.epochSvgElement.nativeElement?.contains(event.target)) {
|
||||
if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
|
||||
this.onPointerMove(event);
|
||||
event.preventDefault();
|
||||
}
|
||||
@@ -202,7 +202,7 @@ export class DifficultyComponent implements OnInit {
|
||||
|
||||
@HostListener('pointermove', ['$event'])
|
||||
onPointerMove(event): void {
|
||||
if (this.epochSvgElement.nativeElement?.contains(event.target)) {
|
||||
if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
|
||||
this.tooltipPosition = { x: event.clientX, y: event.clientY };
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div [formGroup]="fiatForm" class="text-small text-center">
|
||||
<select formControlName="fiat" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 150px;" (change)="changeFiat()">
|
||||
<option *ngFor="let currency of currencies" [value]="currency[1].code">{{ currency[1].name + " (" + currency[1].code + ")" }}</option>
|
||||
<select formControlName="fiat" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 85px;" (change)="changeFiat()">
|
||||
<option *ngFor="let currency of currencies" [value]="currency[1].code">{{ currency[1].code }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MiningService } from '../../services/mining.service';
|
||||
import { download } from '../../shared/graphs.utils';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hashrate-chart',
|
||||
@@ -71,6 +72,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
this.miningWindowPreference = '1y';
|
||||
} else {
|
||||
this.seoService.setTitle($localize`:@@3510fc6daa1d975f331e3a717bdf1a34efa06dff:Hashrate & Difficulty`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.hashrate:See hashrate and difficulty for the Bitcoin${seoDescriptionNetwork(this.network)} network visualized over time.`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('3m');
|
||||
}
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
@@ -256,7 +258,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
let difficultyPowerOfTen = hashratePowerOfTen;
|
||||
let difficulty = tick.data[1];
|
||||
if (difficulty === null) {
|
||||
difficultyString = `${tick.marker} ${tick.seriesName}: No data<br>`;
|
||||
difficultyString = `${tick.marker} ${tick.seriesName}: No data<br>`;
|
||||
} else {
|
||||
if (this.isMobile()) {
|
||||
difficultyPowerOfTen = selectPowerOfTen(tick.data[1]);
|
||||
|
||||
@@ -37,6 +37,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
||||
};
|
||||
windowPreference: string;
|
||||
chartInstance: any = undefined;
|
||||
MA: number[][] = [];
|
||||
weightMode: boolean = false;
|
||||
rateUnitSub: Subscription;
|
||||
|
||||
@@ -62,6 +63,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
||||
return;
|
||||
}
|
||||
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
|
||||
this.MA = this.calculateMA(this.data.series[0]);
|
||||
this.mountChart();
|
||||
}
|
||||
|
||||
@@ -72,7 +74,101 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
/// calculate the moving average of maData
|
||||
calculateMA(maData): number[][] {
|
||||
//update const variables that are not changed
|
||||
const ma: number[][] = [];
|
||||
let sum = 0;
|
||||
let i = 0;
|
||||
const len = maData.length;
|
||||
|
||||
//Adjust window length based on the length of the data
|
||||
//5% appeared as a good amount from tests
|
||||
//TODO: make this a text box in the UI
|
||||
const maWindowLen = Math.ceil(len * 0.05);
|
||||
|
||||
//calculate the center of the moving average window
|
||||
const center = Math.floor(maWindowLen / 2);
|
||||
|
||||
//calculate the centered moving average
|
||||
for (i = center; i < len - center; i++) {
|
||||
sum = 0;
|
||||
//build out ma as we loop through the data
|
||||
ma[i] = [];
|
||||
ma[i].push(maData[i][0]);
|
||||
for (let j = i - center; j <= i + center; j++) {
|
||||
sum += maData[j][1];
|
||||
}
|
||||
|
||||
ma[i].push(sum / maWindowLen);
|
||||
}
|
||||
|
||||
//return the moving average array
|
||||
return ma;
|
||||
}
|
||||
|
||||
mountChart(): void {
|
||||
//create an array for the echart series
|
||||
//similar to how it is done in mempool-graph.component.ts
|
||||
const seriesGraph = [];
|
||||
seriesGraph.push({
|
||||
zlevel: 0,
|
||||
name: 'data',
|
||||
data: this.data.series[0],
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
markLine: {
|
||||
silent: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 1,
|
||||
width: 2,
|
||||
},
|
||||
data: [{
|
||||
yAxis: 1667,
|
||||
label: {
|
||||
show: false,
|
||||
color: '#ffffff',
|
||||
}
|
||||
}],
|
||||
}
|
||||
},
|
||||
{
|
||||
zlevel: 0,
|
||||
name: 'MA',
|
||||
data: this.MA,
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "white",
|
||||
},
|
||||
markLine: {
|
||||
silent: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 1,
|
||||
width: 2,
|
||||
},
|
||||
data: [{
|
||||
yAxis: 1667,
|
||||
label: {
|
||||
show: false,
|
||||
color: '#ffffff',
|
||||
}
|
||||
}],
|
||||
}
|
||||
});
|
||||
|
||||
this.mempoolStatsChartOption = {
|
||||
grid: {
|
||||
height: this.height,
|
||||
@@ -122,16 +218,20 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
||||
type: 'line',
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
|
||||
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
|
||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
|
||||
let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
|
||||
params.map((item: any, index: number) => {
|
||||
if (index < 26) {
|
||||
itemFormatted += `<div class="item">
|
||||
<div class="indicator-container">${colorSpan(item.color)}</div>
|
||||
<div class="grow"></div>
|
||||
<div class="value">${formatNumber(this.weightMode ? item.value[1] * 4 : item.value[1], this.locale, '1.0-0')} <span class="symbol">${this.weightMode ? 'WU' : 'vB'}/s</span></div>
|
||||
</div>`;
|
||||
|
||||
//Do no include MA in tooltip legend!
|
||||
if (item.seriesName !== 'MA') {
|
||||
if (index < 26) {
|
||||
itemFormatted += `<div class="item">
|
||||
<div class="indicator-container">${colorSpan(item.color)}</div>
|
||||
<div class="grow"></div>
|
||||
<div class="value">${formatNumber(item.value[1], this.locale, '1.0-0')}<span class="symbol">vB/s</span></div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
|
||||
@@ -171,35 +271,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
zlevel: 0,
|
||||
data: this.data.series[0],
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
markLine: {
|
||||
silent: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 1,
|
||||
width: 2,
|
||||
},
|
||||
data: [{
|
||||
yAxis: 1667,
|
||||
label: {
|
||||
show: false,
|
||||
color: '#ffffff',
|
||||
}
|
||||
}],
|
||||
}
|
||||
},
|
||||
],
|
||||
series: seriesGraph,
|
||||
visualMap: {
|
||||
show: false,
|
||||
top: 50,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div [formGroup]="languageForm" class="text-small text-center">
|
||||
<select formControlName="language" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 100px;" (change)="changeLanguage()">
|
||||
<select formControlName="language" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 95px;" (change)="changeLanguage()">
|
||||
<option *ngFor="let lang of languages" [value]="lang.code">{{ lang.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
<ng-container *ngIf="{ val: network$ | async } as network">
|
||||
<header *ngIf="headerVisible">
|
||||
<header *ngIf="headerVisible" class="sticky-header">
|
||||
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
||||
<!-- Hamburger -->
|
||||
<ng-container *ngIf="servicesEnabled">
|
||||
<div *ngIf="user" class="profile_image_container" [class]="{'anon': !user.imageMd5}" (click)="hamburgerClick($event)">
|
||||
<img *ngIf="user.imageMd5" [src]="'/api/v1/services/account/image/' + user.username + '?md5=' + user.imageMd5" class="profile_image">
|
||||
<app-svg-images style="color: lightgrey; fill: lightgray" *ngIf="!user.imageMd5" name="anon"></app-svg-images>
|
||||
</div>
|
||||
<div *ngIf="false && user === null" class="profile_image_container" (click)="hamburgerClick($event)">
|
||||
<app-svg-images name="hamburger" height="40"></app-svg-images>
|
||||
</div>
|
||||
<!-- Empty placeholder -->
|
||||
<div *ngIf="user === undefined" class="profile_image_container"></div>
|
||||
</ng-container>
|
||||
|
||||
<a class="navbar-brand" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||
<ng-template [ngIf]="subdomain">
|
||||
<div class="subdomain_container">
|
||||
@@ -62,11 +76,19 @@
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'signet'"></app-testnet-alert>
|
||||
<div class="d-flex" style="overflow: clip">
|
||||
<app-menu *ngIf="servicesEnabled" [navOpen]="menuOpen" (loggedOut)="onLoggedOut()" (menuToggled)="menuToggled($event)"></app-menu>
|
||||
|
||||
<main>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
<div class="flex-grow-1 d-flex flex-column">
|
||||
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'signet'"></app-testnet-alert>
|
||||
|
||||
<main style="min-width: 375px" [style]="menuOpen ? 'max-width: calc(100vw - 225px)' : 'max-width: 100vw'">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<div class="flex-grow-1"></div>
|
||||
<app-global-footer *ngIf="footerVisible"></app-global-footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-global-footer *ngIf="footerVisible"></app-global-footer>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
.sticky-header {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
li.nav-item.active {
|
||||
background-color: #653b9c;
|
||||
}
|
||||
@@ -86,7 +94,6 @@ li.nav-item {
|
||||
|
||||
.navbar-brand {
|
||||
position: relative;
|
||||
height: 65px;
|
||||
}
|
||||
|
||||
.navbar-brand.dual-logos {
|
||||
@@ -102,7 +109,7 @@ nav {
|
||||
|
||||
.connection-badge {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
top: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -209,4 +216,26 @@ nav {
|
||||
margin-left: 5px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile_image_container {
|
||||
width: 35px;
|
||||
margin-right: 15px;
|
||||
text-align: center;
|
||||
align-self: center;
|
||||
cursor: pointer;
|
||||
&.anon {
|
||||
border: 1.5px solid lightgrey;
|
||||
color: lightgrey;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
.profile_image {
|
||||
height: 35px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
main {
|
||||
transition: 0.2s;
|
||||
transition-property: max-width;
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Component, OnInit, Input, ViewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Env, StateService } from '../../services/state.service';
|
||||
import { Observable, merge, of } from 'rxjs';
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
import { EnterpriseService } from '../../services/enterprise.service';
|
||||
import { NavigationService } from '../../services/navigation.service';
|
||||
import { MenuComponent } from '../menu/menu.component';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-master-page',
|
||||
@@ -25,12 +29,21 @@ export class MasterPageComponent implements OnInit {
|
||||
networkPaths: { [network: string]: string };
|
||||
networkPaths$: Observable<Record<string, string>>;
|
||||
footerVisible = true;
|
||||
user: any = undefined;
|
||||
servicesEnabled = false;
|
||||
menuOpen = false;
|
||||
|
||||
@ViewChild(MenuComponent)
|
||||
public menuComponent!: MenuComponent;
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private languageService: LanguageService,
|
||||
private enterpriseService: EnterpriseService,
|
||||
private navigationService: NavigationService,
|
||||
private storageService: StorageService,
|
||||
private apiService: ApiService,
|
||||
private router: Router,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -51,17 +64,47 @@ export class MasterPageComponent implements OnInit {
|
||||
this.footerVisible = this.footerVisibleOverride;
|
||||
}
|
||||
});
|
||||
|
||||
this.servicesEnabled = this.officialMempoolSpace && this.stateService.env.ACCELERATOR === true && this.stateService.network === '';
|
||||
this.refreshAuth();
|
||||
|
||||
const isServicesPage = this.router.url.includes('/services/');
|
||||
this.menuOpen = isServicesPage && !this.isSmallScreen();
|
||||
}
|
||||
|
||||
collapse(): void {
|
||||
this.navCollapsed = !this.navCollapsed;
|
||||
}
|
||||
|
||||
isSmallScreen() {
|
||||
return window.innerWidth <= 767.98;
|
||||
}
|
||||
|
||||
onResize(): void {
|
||||
this.isMobile = window.innerWidth <= 767.98;
|
||||
this.isMobile = this.isSmallScreen();
|
||||
}
|
||||
|
||||
brandClick(e): void {
|
||||
this.stateService.resetScroll$.next(true);
|
||||
}
|
||||
|
||||
onLoggedOut(): void {
|
||||
this.refreshAuth();
|
||||
}
|
||||
|
||||
refreshAuth(): void {
|
||||
this.user = this.storageService.getAuth()?.user ?? null;
|
||||
}
|
||||
|
||||
hamburgerClick(event): void {
|
||||
if (this.menuComponent) {
|
||||
this.menuComponent.hamburgerClick();
|
||||
this.menuOpen = this.menuComponent.navOpen;
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
menuToggled(isOpen: boolean): void {
|
||||
this.menuOpen = isOpen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { switchMap, map, tap, filter } from 'rxjs/operators';
|
||||
import { MempoolBlock, TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
|
||||
@Component({
|
||||
@@ -54,6 +55,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
||||
const ordinal = this.getOrdinal(mempoolBlocks[this.mempoolBlockIndex]);
|
||||
this.ordinal$.next(ordinal);
|
||||
this.seoService.setTitle(ordinal);
|
||||
this.seoService.setDescription($localize`:@@meta.description.mempool-block:See stats for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transactions in the mempool: fee range, aggregate size, and more. Mempool blocks are updated in real-time as the network receives new transactions.`);
|
||||
mempoolBlocks[this.mempoolBlockIndex].isStack = mempoolBlocks[this.mempoolBlockIndex].blockVSize > this.stateService.blockVSize;
|
||||
return mempoolBlocks[this.mempoolBlockIndex];
|
||||
})
|
||||
|
||||
@@ -97,6 +97,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
ngOnInit() {
|
||||
this.chainTip = this.stateService.latestBlockHeight;
|
||||
|
||||
const width = this.containerOffset + (this.stateService.env.MEMPOOL_BLOCKS_AMOUNT) * this.blockOffset;
|
||||
this.mempoolWidth = width;
|
||||
this.widthChange.emit(this.mempoolWidth);
|
||||
|
||||
if (['', 'testnet', 'signet'].includes(this.stateService.network)) {
|
||||
this.enabledMiningInfoIfNeeded(this.location.path());
|
||||
this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url));
|
||||
@@ -161,11 +165,11 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return this.mempoolBlocks;
|
||||
}),
|
||||
tap(() => {
|
||||
this.cd.markForCheck();
|
||||
const width = this.containerOffset + this.mempoolBlocks.length * this.blockOffset;
|
||||
if (this.mempoolWidth !== width) {
|
||||
this.mempoolWidth = width;
|
||||
this.widthChange.emit(this.mempoolWidth);
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -215,11 +219,13 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (isNewBlock && (block?.extras?.similarity == null || block?.extras?.similarity > 0.5) && !this.tabHidden) {
|
||||
this.blockIndex++;
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.chainTipSubscription = this.stateService.chainTip$.subscribe((height) => {
|
||||
if (this.chainTip === -1) {
|
||||
this.chainTip = height;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -257,6 +263,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.blockPadding = 0.24 * this.blockWidth;
|
||||
this.containerOffset = 0.32 * this.blockWidth;
|
||||
this.blockOffset = this.blockWidth + this.blockPadding;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +282,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
onResize(): void {
|
||||
this.animateEntry = false;
|
||||
this.reduceEmptyBlocksToFitScreen(this.mempoolEmptyBlocks);
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
trackByFn(index: number, block: MempoolBlock) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
|
||||
import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe';
|
||||
import { WuBytesPipe } from '../../shared/pipes/bytes-pipe/wubytes.pipe';
|
||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
||||
import { formatNumber } from '@angular/common';
|
||||
import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
|
||||
import { StateService } from '../../services/state.service';
|
||||
@@ -26,6 +27,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
@Input() data: any[];
|
||||
@Input() filterSize = 100000;
|
||||
@Input() limitFilterFee = 1;
|
||||
@Input() hideCount: boolean = false;
|
||||
@Input() height: number | string = 200;
|
||||
@Input() top: number | string = 20;
|
||||
@Input() right: number | string = 10;
|
||||
@@ -50,10 +52,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
inverted: boolean;
|
||||
chartInstance: any = undefined;
|
||||
weightMode: boolean = false;
|
||||
isWidget: boolean = false;
|
||||
showCount: boolean = true;
|
||||
|
||||
constructor(
|
||||
private vbytesPipe: VbytesPipe,
|
||||
private wubytesPipe: WuBytesPipe,
|
||||
private amountShortenerPipe: AmountShortenerPipe,
|
||||
private stateService: StateService,
|
||||
private storageService: StorageService,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
@@ -62,12 +67,16 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
ngOnInit(): void {
|
||||
this.isLoading = true;
|
||||
this.inverted = this.storageService.getValue('inverted-graph') === 'true';
|
||||
this.isWidget = this.template === 'widget';
|
||||
this.showCount = !this.isWidget && !this.hideCount;
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
ngOnChanges(changes) {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
this.isWidget = this.template === 'widget';
|
||||
this.showCount = !this.isWidget && !this.hideCount;
|
||||
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
|
||||
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
||||
this.mountFeeChart();
|
||||
@@ -96,10 +105,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
mempoolStats.reverse();
|
||||
const labels = mempoolStats.map(stats => stats.added);
|
||||
const finalArrayVByte = this.generateArray(mempoolStats);
|
||||
const finalArrayCount = this.generateCountArray(mempoolStats);
|
||||
|
||||
return {
|
||||
labels: labels,
|
||||
series: finalArrayVByte
|
||||
series: finalArrayVByte,
|
||||
countSeries: finalArrayCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,9 +135,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
return finalArray;
|
||||
}
|
||||
|
||||
generateCountArray(mempoolStats: OptimizedMempoolStats[]) {
|
||||
return mempoolStats.filter(stats => stats.count > 0).map(stats => [stats.added * 1000, stats.count]);
|
||||
}
|
||||
|
||||
mountFeeChart() {
|
||||
this.orderLevels();
|
||||
const { series } = this.mempoolVsizeFeesData;
|
||||
const { series, countSeries } = this.mempoolVsizeFeesData;
|
||||
|
||||
const seriesGraph = [];
|
||||
const newColors = [];
|
||||
@@ -178,6 +193,29 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.showCount) {
|
||||
newColors.push('white');
|
||||
seriesGraph.push({
|
||||
zlevel: 1,
|
||||
yAxisIndex: 1,
|
||||
name: 'count',
|
||||
type: 'line',
|
||||
stack: 'count',
|
||||
smooth: false,
|
||||
markPoint: false,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
opacity: 1,
|
||||
},
|
||||
symbol: 'none',
|
||||
silent: true,
|
||||
areaStyle: {
|
||||
color: null,
|
||||
opacity: 0,
|
||||
},
|
||||
data: countSeries,
|
||||
});
|
||||
}
|
||||
|
||||
this.mempoolVsizeFeesOptions = {
|
||||
series: this.inverted ? [...seriesGraph].reverse() : seriesGraph,
|
||||
@@ -201,7 +239,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
label: {
|
||||
formatter: (params: any) => {
|
||||
if (params.axisDimension === 'y') {
|
||||
return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true)
|
||||
if (params.axisIndex === 0) {
|
||||
return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true);
|
||||
} else {
|
||||
return this.amountShortenerPipe.transform(params.value, 2, undefined, true);
|
||||
}
|
||||
} else {
|
||||
return formatterXAxis(this.locale, this.windowPreference, params.value);
|
||||
}
|
||||
@@ -214,7 +256,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
const itemFormatted = [];
|
||||
let totalParcial = 0;
|
||||
let progressPercentageText = '';
|
||||
const items = this.inverted ? [...params].reverse() : params;
|
||||
let countItem;
|
||||
let items = this.inverted ? [...params].reverse() : params;
|
||||
if (items[items.length - 1].seriesName === 'count') {
|
||||
countItem = items.pop();
|
||||
}
|
||||
items.map((item: any, index: number) => {
|
||||
totalParcial += item.value[1];
|
||||
const progressPercentage = (item.value[1] / totalValue) * 100;
|
||||
@@ -276,6 +322,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
</tr>`);
|
||||
});
|
||||
const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : '';
|
||||
const titleCount = $localize`Count`;
|
||||
const titleRange = $localize`Range`;
|
||||
const titleSize = $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`;
|
||||
const titleSum = $localize`Sum`;
|
||||
@@ -286,6 +333,25 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
|
||||
</span>
|
||||
</div>
|
||||
` +
|
||||
(this.showCount && countItem ? `
|
||||
<table class="count">
|
||||
<tbody>
|
||||
<tr class="item">
|
||||
<td class="indicator-container">
|
||||
<span class="indicator" style="background-color: white"></span>
|
||||
<span>
|
||||
${titleCount}
|
||||
</span>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<span>${this.amountShortenerPipe.transform(countItem.value[1], 2, undefined, true)}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
` : '')
|
||||
+ `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -305,12 +371,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
</div>`;
|
||||
}
|
||||
},
|
||||
dataZoom: (this.template === 'widget' && this.isMobile()) ? null : [{
|
||||
dataZoom: (this.isWidget && this.isMobile()) ? null : [{
|
||||
type: 'inside',
|
||||
realtime: true,
|
||||
zoomLock: (this.template === 'widget') ? true : false,
|
||||
zoomLock: (this.isWidget) ? true : false,
|
||||
zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
|
||||
moveOnMouseMove: (this.template === 'widget') ? true : false,
|
||||
moveOnMouseMove: (this.isWidget) ? true : false,
|
||||
maxSpan: 100,
|
||||
minSpan: 10,
|
||||
}, {
|
||||
@@ -339,7 +405,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
name: this.template === 'widget' ? '' : formatterXAxisLabel(this.locale, this.windowPreference),
|
||||
name: this.isWidget ? '' : formatterXAxisLabel(this.locale, this.windowPreference),
|
||||
nameLocation: 'middle',
|
||||
nameTextStyle: {
|
||||
padding: [20, 0, 0, 0],
|
||||
@@ -357,7 +423,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
},
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
axisLine: { onZero: false },
|
||||
axisLabel: {
|
||||
@@ -371,7 +437,17 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
},
|
||||
}, this.showCount ? {
|
||||
type: 'value',
|
||||
position: 'right',
|
||||
axisLine: { onZero: false },
|
||||
axisLabel: {
|
||||
formatter: (value: number) => (`${this.amountShortenerPipe.transform(value, 2, undefined, true)}`),
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
}
|
||||
} : null],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
31
frontend/src/app/components/menu/menu.component.html
Normal file
31
frontend/src/app/components/menu/menu.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="sidenav menu-click" [class]="navOpen ? 'open': ''">
|
||||
<div class="d-flex menu-click">
|
||||
|
||||
<nav class="scrollable menu-click">
|
||||
<span *ngIf="userAuth" class="menu-click">
|
||||
<strong class="menu-click">@ {{ userAuth.user.username }}</strong>
|
||||
</span>
|
||||
<a *ngIf="!userAuth" class="d-flex justify-content-center align-items-center nav-link m-0 menu-click" routerLink="/login" role="tab" (click)="onLinkClick('/login')">
|
||||
<fa-icon class="menu-click" [icon]="['fas', 'user-circle']" [fixedWidth]="true" style="font-size: 25px;margin-right: 15px;"></fa-icon>
|
||||
<span class="menu-click" style="font-size: 20px;">Sign in</span>
|
||||
</a>
|
||||
|
||||
<ng-container *ngIf="userMenuGroups$ | async as menuGroups">
|
||||
<div class="menu-click" *ngFor="let group of menuGroups" style="height: max-content;">
|
||||
<h6 class="d-flex justify-content-between align-items-center mt-4 mb-2 text-uppercase menu-click">
|
||||
<span class="menu-click">{{ group.title }}</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column menu-click" *ngFor="let item of group.items" (click)="onLinkClick(item.link)">
|
||||
<li class="nav-item d-flex justify-content-start align-items-center menu-click">
|
||||
<fa-icon class="menu-click" [icon]="['fas', item.faIcon]" [fixedWidth]="true"></fa-icon>
|
||||
<button *ngIf="item.link === 'logout'" class="btn nav-link menu-click" role="tab" (click)="logout()">{{ item.title }}</button>
|
||||
<a *ngIf="item.title !== 'Logout'" class="nav-link menu-click" [routerLink]="[item.link]" role="tab">{{ item.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ng-container>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
48
frontend/src/app/components/menu/menu.component.scss
Normal file
48
frontend/src/app/components/menu/menu.component.scss
Normal file
@@ -0,0 +1,48 @@
|
||||
.sidenav {
|
||||
z-index: 1;
|
||||
background-color: transparent;
|
||||
width: 225px;
|
||||
height: calc(100vh - 65px);
|
||||
position: sticky;
|
||||
top: 65px;
|
||||
transition: 0.25s;
|
||||
margin-left: -250px;
|
||||
box-shadow: 5px 0px 30px 0px #000;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.sidenav.open {
|
||||
margin-left: 0px;
|
||||
left: 0px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidenav a, button{
|
||||
text-decoration: none;
|
||||
color: lightgray;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.sidenav a:hover {
|
||||
color: white;
|
||||
}
|
||||
.sidenav nav {
|
||||
width: 100%;
|
||||
height: calc(100vh - 65px);
|
||||
background-color: #1d1f31;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
@media (max-width: 991px) {
|
||||
padding-bottom: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 450px) {
|
||||
.sidenav a {font-size: 18px;}
|
||||
}
|
||||
101
frontend/src/app/components/menu/menu.component.ts
Normal file
101
frontend/src/app/components/menu/menu.component.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter, HostListener, OnDestroy } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { MenuGroup } from '../../interfaces/services.interface';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-menu',
|
||||
templateUrl: './menu.component.html',
|
||||
styleUrls: ['./menu.component.scss']
|
||||
})
|
||||
|
||||
export class MenuComponent implements OnInit, OnDestroy {
|
||||
@Input() navOpen: boolean = false;
|
||||
@Output() loggedOut = new EventEmitter<boolean>();
|
||||
@Output() menuToggled = new EventEmitter<boolean>();
|
||||
|
||||
userMenuGroups$: Observable<MenuGroup[]> | undefined;
|
||||
userAuth: any | undefined;
|
||||
isServicesPage = false;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private storageService: StorageService,
|
||||
private router: Router,
|
||||
private stateService: StateService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userAuth = this.storageService.getAuth();
|
||||
|
||||
if (this.stateService.env.GIT_COMMIT_HASH_MEMPOOL_SPACE) {
|
||||
this.userMenuGroups$ = this.apiService.getUserMenuGroups$();
|
||||
}
|
||||
|
||||
this.isServicesPage = this.router.url.includes('/services/');
|
||||
this.router.events.subscribe((event) => {
|
||||
if (event instanceof NavigationStart) {
|
||||
if (!this.isServicesPage) {
|
||||
this.toggleMenu(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleMenu(toggled: boolean) {
|
||||
this.navOpen = toggled;
|
||||
this.menuToggled.emit(toggled);
|
||||
}
|
||||
|
||||
isSmallScreen() {
|
||||
return window.innerWidth <= 767.98;
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.apiService.logout$().subscribe(() => {
|
||||
this.loggedOut.emit(true);
|
||||
if (this.stateService.env.GIT_COMMIT_HASH_MEMPOOL_SPACE) {
|
||||
this.userMenuGroups$ = this.apiService.getUserMenuGroups$();
|
||||
this.router.navigateByUrl('/');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onLinkClick(link) {
|
||||
if (!this.isServicesPage || this.isSmallScreen()) {
|
||||
this.toggleMenu(false);
|
||||
}
|
||||
this.router.navigateByUrl(link);
|
||||
}
|
||||
|
||||
hamburgerClick() {
|
||||
this.toggleMenu(!this.navOpen);
|
||||
this.stateService.menuOpen$.next(this.navOpen);
|
||||
}
|
||||
|
||||
@HostListener('window:click', ['$event'])
|
||||
onClick(event) {
|
||||
const isServicesPageOnMobile = this.isServicesPage && this.isSmallScreen();
|
||||
const cssClasses = event.target.className;
|
||||
|
||||
if (!cssClasses.indexOf) { // Click on chart or non html thingy, close the menu
|
||||
if (!this.isServicesPage || isServicesPageOnMobile) {
|
||||
this.toggleMenu(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const isHamburger = cssClasses.indexOf('profile_image') !== -1;
|
||||
const isMenu = cssClasses.indexOf('menu-click') !== -1;
|
||||
if (!isHamburger && !isMenu && (!this.isServicesPage || isServicesPageOnMobile)) {
|
||||
this.toggleMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.stateService.menuOpen$.next(false);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ export class MiningDashboardComponent implements OnInit, AfterViewInit {
|
||||
private router: Router
|
||||
) {
|
||||
this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Mining Dashboard`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.mining.dashboard:Get real-time Bitcoin mining stats like hashrate, difficulty adjustment, block rewards, pool dominance, and more.`);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -29,7 +30,7 @@ export class MiningDashboardComponent implements OnInit, AfterViewInit {
|
||||
this.router.events.subscribe((e: NavigationStart) => {
|
||||
if (e.type === EventType.NavigationStart) {
|
||||
if (e.url.indexOf('graphs') === -1) { // The mining dashboard and the graph component are part of the same module so we can't use ngAfterViewInit in graphs.component.ts to blur the input
|
||||
this.stateService.focusSearchInputDesktop();
|
||||
this.stateService.focusSearchInputDesktop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -56,6 +56,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
this.miningWindowPreference = '1w';
|
||||
} else {
|
||||
this.seoService.setTitle($localize`:@@mining.mining-pools:Mining Pools`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.pool-ranking:See the top Bitcoin mining pools ranked by number of blocks mined, over your desired timeframe.`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
|
||||
}
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
@@ -116,7 +117,7 @@ export class PoolRankingComponent implements OnInit {
|
||||
} else if (this.widget) {
|
||||
poolShareThreshold = 1;
|
||||
}
|
||||
|
||||
|
||||
const data: object[] = [];
|
||||
let totalShareOther = 0;
|
||||
let totalBlockOther = 0;
|
||||
|
||||
@@ -83,6 +83,7 @@ export class PoolPreviewComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.seoService.setTitle(poolStats.pool.name);
|
||||
this.seoService.setDescription($localize`:@@meta.description.mining.pool:See mining pool stats for ${poolStats.pool.name}\: most recent mined blocks, hashrate over time, total block reward to date, known coinbase addresses, and more.`);
|
||||
let regexes = '"';
|
||||
for (const regex of poolStats.pool.regexes) {
|
||||
regexes += regex + '", "';
|
||||
|
||||
@@ -83,6 +83,7 @@ export class PoolComponent implements OnInit {
|
||||
}),
|
||||
map((poolStats) => {
|
||||
this.seoService.setTitle(poolStats.pool.name);
|
||||
this.seoService.setDescription($localize`:@@meta.description.mining.pool:See mining pool stats for ${poolStats.pool.name}\: most recent mined blocks, hashrate over time, total block reward to date, known coinbase addresses, and more.`);
|
||||
let regexes = '"';
|
||||
for (const regex of poolStats.pool.regexes) {
|
||||
regexes += regex + '", "';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user