Compare commits
332 Commits
v3.0.0-dev
...
natsoni/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
955607729b | ||
|
|
2297ff72f5 | ||
|
|
fbaa39a300 | ||
|
|
bd2c15c6bb | ||
|
|
bd5a23ff0d | ||
|
|
dd0c1eb74d | ||
|
|
9950ef16ce | ||
|
|
fed2c034ee | ||
|
|
4944e3c257 | ||
|
|
b865fa33f6 | ||
|
|
bb44ed15da | ||
|
|
93919421ee | ||
|
|
8d1831129b | ||
|
|
5e6e2c037e | ||
|
|
ddfceddc57 | ||
|
|
5b19ffd524 | ||
|
|
7422001ed5 | ||
|
|
3b0ef48cdf | ||
|
|
d13875e3e7 | ||
|
|
6ebc7fab47 | ||
|
|
9d931c1d13 | ||
|
|
f86b6ac3de | ||
|
|
ce1124284e | ||
|
|
233f19d23d | ||
|
|
89ef5fe33d | ||
|
|
7cd4345264 | ||
|
|
cee1b39640 | ||
|
|
e388410e34 | ||
|
|
c2d649d3f4 | ||
|
|
d9dec44c8f | ||
|
|
c94a3a98eb | ||
|
|
ec9b8359df | ||
|
|
3db89e3dee | ||
|
|
7f8dc74146 | ||
|
|
83cea33727 | ||
|
|
ada18a0413 | ||
|
|
96d85dcacd | ||
|
|
64ba033602 | ||
|
|
26807e80db | ||
|
|
e4f00285b3 | ||
|
|
d443e51d30 | ||
|
|
b4352f5f25 | ||
|
|
2c1b4a2547 | ||
|
|
b54685bed7 | ||
|
|
9032d5ac11 | ||
|
|
942747a0be | ||
|
|
fdd6463ac0 | ||
|
|
e9afc4ec0f | ||
|
|
dff811f615 | ||
|
|
d1e382ddf7 | ||
|
|
ccd460cf70 | ||
|
|
7b1ba912be | ||
|
|
f3864c9100 | ||
|
|
1082889b64 | ||
|
|
36839fdcb9 | ||
|
|
ebe54d47d8 | ||
|
|
7219823847 | ||
|
|
336fc7a64a | ||
|
|
84f62f8025 | ||
|
|
6bd6dfec49 | ||
|
|
a6e27f1312 | ||
|
|
c913df837a | ||
|
|
42bd9811e3 | ||
|
|
7fbf13fd9d | ||
|
|
fb086b5ad5 | ||
|
|
d4f51979d4 | ||
|
|
aeb54f59f1 | ||
|
|
5ca3b24527 | ||
|
|
88df2878cb | ||
|
|
f601bbc499 | ||
|
|
24e9ae6440 | ||
|
|
8c3f11622c | ||
|
|
da25ed431f | ||
|
|
ecc234a96a | ||
|
|
29851537eb | ||
|
|
5f2d7b32ae | ||
|
|
de00d49d7b | ||
|
|
bafa0f50fc | ||
|
|
80c75f9aeb | ||
|
|
407eba194d | ||
|
|
2eac3e6555 | ||
|
|
61a308cbc6 | ||
|
|
49b9a6f53d | ||
|
|
db1cc0d0e1 | ||
|
|
f89bd5b972 | ||
|
|
d94a8cb58c | ||
|
|
faf79e85fd | ||
|
|
0d72e88c6a | ||
|
|
786ec7fff1 | ||
|
|
8df497e53d | ||
|
|
1dd7a6ebac | ||
|
|
2c12e9f64b | ||
|
|
9a77135d30 | ||
|
|
f3b3b9b3f0 | ||
|
|
55c4d4d03d | ||
|
|
061d341d8b | ||
|
|
1c29b8b260 | ||
|
|
79bfe9c866 | ||
|
|
3e6d38656d | ||
|
|
5697486cea | ||
|
|
df0da605e4 | ||
|
|
fa9aaf0423 | ||
|
|
89de288fec | ||
|
|
940003552b | ||
|
|
0660296b51 | ||
|
|
affeb0a169 | ||
|
|
2504653426 | ||
|
|
b6a9ad67d3 | ||
|
|
3d4741eac2 | ||
|
|
5611f57e9e | ||
|
|
44027f5bc0 | ||
|
|
41b25a78e9 | ||
|
|
e263f94765 | ||
|
|
80c7754e48 | ||
|
|
165340324c | ||
|
|
735dddd604 | ||
|
|
3e3bd32705 | ||
|
|
f83025a9ff | ||
|
|
ba6a7b58aa | ||
|
|
fb8808ea59 | ||
|
|
73f241e9c3 | ||
|
|
317b1b6ac5 | ||
|
|
cabe629f17 | ||
|
|
9f6521b987 | ||
|
|
6daffe5b13 | ||
|
|
4c7f93d1ef | ||
|
|
f81bbb93a5 | ||
|
|
b0058e94ce | ||
|
|
de069f704a | ||
|
|
bcb8493cd0 | ||
|
|
e55b1740db | ||
|
|
df673b2a4e | ||
|
|
a4753769d2 | ||
|
|
be75a87e88 | ||
|
|
0ac3ae1cb1 | ||
|
|
a5e1e95534 | ||
|
|
7f6ab0b854 | ||
|
|
8420ecd380 | ||
|
|
192fd09a00 | ||
|
|
d9a59c6d1e | ||
|
|
69c3c3162c | ||
|
|
61ba832dfd | ||
|
|
f332bba468 | ||
|
|
ba8cca6ba5 | ||
|
|
7a098952c8 | ||
|
|
347b74a55d | ||
|
|
d5b0adeeed | ||
|
|
d8c4d36d4b | ||
|
|
aac32c5bff | ||
|
|
ff86e55622 | ||
|
|
894c4cb139 | ||
|
|
2792016383 | ||
|
|
46215871aa | ||
|
|
cfc06be975 | ||
|
|
527589ac04 | ||
|
|
43845cda5c | ||
|
|
b74b8a8a5a | ||
|
|
226c6d8432 | ||
|
|
9f79258dec | ||
|
|
13bcc99095 | ||
|
|
fef9b93a05 | ||
|
|
ccac3437cf | ||
|
|
e849a31668 | ||
|
|
88de5412f8 | ||
|
|
d13c48d31d | ||
|
|
4c807866a3 | ||
|
|
d7a4a95c05 | ||
|
|
b35422ff9f | ||
|
|
b6be5d71bb | ||
|
|
4c5eddcf6d | ||
|
|
51f0b75a64 | ||
|
|
fe4648cd9e | ||
|
|
03867ada49 | ||
|
|
7959188c06 | ||
|
|
11eaa0ca50 | ||
|
|
dc6dba416a | ||
|
|
c8e7cc773a | ||
|
|
efe43329a1 | ||
|
|
3f97c17af2 | ||
|
|
91493e8769 | ||
|
|
5f36cb88ab | ||
|
|
a0ef635c92 | ||
|
|
d68904fec0 | ||
|
|
b679581cf2 | ||
|
|
f7e6fa026d | ||
|
|
96f16f1f2e | ||
|
|
ad8fa8722f | ||
|
|
e477f09cd5 | ||
|
|
be5eb9ef70 | ||
|
|
b952642570 | ||
|
|
47cc74a351 | ||
|
|
cff572a104 | ||
|
|
48e16e64c2 | ||
|
|
46172836f1 | ||
|
|
eacb72a05b | ||
|
|
c1fefaab92 | ||
|
|
f2f86457ee | ||
|
|
c251b5831b | ||
|
|
8c589d3000 | ||
|
|
ddc599f6b7 | ||
|
|
6822c3a04b | ||
|
|
aa0c70bd44 | ||
|
|
94f649b345 | ||
|
|
5e07e9dceb | ||
|
|
91a8a8be34 | ||
|
|
99e1890795 | ||
|
|
5583befbba | ||
|
|
c637055859 | ||
|
|
c3acfb8781 | ||
|
|
fd073a7043 | ||
|
|
bed00fbd41 | ||
|
|
501b79fdce | ||
|
|
ed6af8f560 | ||
|
|
32495736d4 | ||
|
|
bff48b0a64 | ||
|
|
f6228240ba | ||
|
|
215c8a7ff4 | ||
|
|
0f217bd753 | ||
|
|
7e920f4bae | ||
|
|
cde3d878b1 | ||
|
|
621a6ea42d | ||
|
|
cfc3615e64 | ||
|
|
2f8d0d90cd | ||
|
|
c827953ca5 | ||
|
|
79dd263fb1 | ||
|
|
1ca05a029a | ||
|
|
4c205eb09d | ||
|
|
ee92f6639a | ||
|
|
7721f16f9f | ||
|
|
854222b8cc | ||
|
|
0bc86541c6 | ||
|
|
c45111333d | ||
|
|
ba6fedc430 | ||
|
|
caa34fa8bb | ||
|
|
fa7c0ab58e | ||
|
|
2543eb6861 | ||
|
|
c4cf3363ee | ||
|
|
98d4efd509 | ||
|
|
a99e65ee48 | ||
|
|
3fd4d24c93 | ||
|
|
55a564a5a8 | ||
|
|
04f6490eeb | ||
|
|
b5026789d6 | ||
|
|
705e570cf5 | ||
|
|
10a41fb0d1 | ||
|
|
2a591646c3 | ||
|
|
e55898b414 | ||
|
|
2df476406d | ||
|
|
4a05b35f2b | ||
|
|
3ad8e56c25 | ||
|
|
c1704758fd | ||
|
|
fe580f2b2b | ||
|
|
d82e482acc | ||
|
|
9d6142dc79 | ||
|
|
2dec83735b | ||
|
|
9f4204b815 | ||
|
|
40f2c972d7 | ||
|
|
eee2385d0d | ||
|
|
492c652157 | ||
|
|
af8e060dc6 | ||
|
|
6a19b9058f | ||
|
|
bcbd21b922 | ||
|
|
aac76d68b0 | ||
|
|
25bb942e04 | ||
|
|
8ac71f21d1 | ||
|
|
287559f028 | ||
|
|
7fbb93cf41 | ||
|
|
7b76e93631 | ||
|
|
896c451c3e | ||
|
|
0874386001 | ||
|
|
ce6808b3c4 | ||
|
|
40fa1c5acb | ||
|
|
7307990f6f | ||
|
|
5104da500e | ||
|
|
3a8c46bbed | ||
|
|
fc5312549d | ||
|
|
c14e8797e2 | ||
|
|
0f26940018 | ||
|
|
26227e2f3b | ||
|
|
dcf78fab06 | ||
|
|
0813592a6d | ||
|
|
abdb27af3f | ||
|
|
b421be3315 | ||
|
|
b74fbee069 | ||
|
|
dab9357b40 | ||
|
|
ccd89604c0 | ||
|
|
cd135b7171 | ||
|
|
7e16d550b0 | ||
|
|
404079ef4e | ||
|
|
d13f78f046 | ||
|
|
60040c3914 | ||
|
|
e408fbd8d6 | ||
|
|
6931ecd468 | ||
|
|
ed5d30ea5b | ||
|
|
ead7613579 | ||
|
|
ca0613ec17 | ||
|
|
727208ff84 | ||
|
|
53c3de2af5 | ||
|
|
3f50d57ed1 | ||
|
|
55f9d0f875 | ||
|
|
37cb9b0fe8 | ||
|
|
a3b5f79094 | ||
|
|
1b1ffa7109 | ||
|
|
2b3021c8fe | ||
|
|
0ccc47786d | ||
|
|
24366b929e | ||
|
|
2cab2a7885 | ||
|
|
4284038c4b | ||
|
|
425d158777 | ||
|
|
1a11b1813f | ||
|
|
85a86d4b06 | ||
|
|
073578243c | ||
|
|
69578f8086 | ||
|
|
ec92f83a58 | ||
|
|
37c0adbbfa | ||
|
|
e9c40692a6 | ||
|
|
4efd0dda83 | ||
|
|
995a26b944 | ||
|
|
3a2aad645b | ||
|
|
9908ec01aa | ||
|
|
3d900bdfe5 | ||
|
|
b32cce1440 | ||
|
|
37aee77eb4 | ||
|
|
96ba7d0656 | ||
|
|
6852319e4d | ||
|
|
8bc1eaebc0 | ||
|
|
eeefaa6374 | ||
|
|
fb6aec0afe | ||
|
|
b8a48314c1 | ||
|
|
f4b9301f55 | ||
|
|
1eb52d8a35 | ||
|
|
8fee195577 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
|
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
|
||||||
# Latest version available on this commit is 1.71.1
|
# Latest version available on this commit is 1.71.1
|
||||||
# Commit date is Aug 3, 2023
|
# Commit date is Aug 3, 2023
|
||||||
uses: dtolnay/rust-toolchain@dc6353516c68da0f06325f42ad880f76a5e77ec9
|
uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
|
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/on-tag.yml
vendored
2
.github/workflows/on-tag.yml
vendored
@@ -101,5 +101,7 @@ jobs:
|
|||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
|
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
|
||||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
|
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
|
||||||
|
--build-context rustgbt=./rust \
|
||||||
|
--build-context backend=./backend \
|
||||||
--output "type=registry" ./${{ matrix.service }}/ \
|
--output "type=registry" ./${{ matrix.service }}/ \
|
||||||
--build-arg commitHash=$SHORT_SHA
|
--build-arg commitHash=$SHORT_SHA
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ In particular, make sure:
|
|||||||
- the correct Bitcoin Core RPC credentials are specified in `CORE_RPC`
|
- the correct Bitcoin Core RPC credentials are specified in `CORE_RPC`
|
||||||
- the correct `BACKEND` is specified in `MEMPOOL`:
|
- the correct `BACKEND` is specified in `MEMPOOL`:
|
||||||
- "electrum" if you're using [romanz/electrs](https://github.com/romanz/electrs) or [cculianu/Fulcrum](https://github.com/cculianu/Fulcrum)
|
- "electrum" if you're using [romanz/electrs](https://github.com/romanz/electrs) or [cculianu/Fulcrum](https://github.com/cculianu/Fulcrum)
|
||||||
- "esplora" if you're using [Blockstream/electrs](https://github.com/Blockstream/electrs)
|
- "esplora" if you're using [mempool/electrs](https://github.com/mempool/electrs)
|
||||||
- "none" if you're not using any Electrum Server
|
- "none" if you're not using any Electrum Server
|
||||||
|
|
||||||
### 6. Run Mempool Backend
|
### 6. Run Mempool Backend
|
||||||
|
|||||||
@@ -35,7 +35,8 @@
|
|||||||
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
||||||
"ALLOW_UNREACHABLE": true,
|
"ALLOW_UNREACHABLE": true,
|
||||||
"PRICE_UPDATES_PER_HOUR": 1,
|
"PRICE_UPDATES_PER_HOUR": 1,
|
||||||
"MAX_TRACKED_ADDRESSES": 100
|
"MAX_TRACKED_ADDRESSES": 100,
|
||||||
|
"UNIX_SOCKET_PATH": ""
|
||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
@@ -151,6 +152,7 @@
|
|||||||
},
|
},
|
||||||
"FIAT_PRICE": {
|
"FIAT_PRICE": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
"PAID": false,
|
||||||
"API_KEY": "your-api-key-from-freecurrencyapi.com"
|
"API_KEY": "your-api-key-from-freecurrencyapi.com"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
# Cleaning up inside the node_modules folder
|
# Cleaning up inside the node_modules folder
|
||||||
cd package/node_modules
|
cd package/node_modules
|
||||||
rm -r \
|
rm -rf \
|
||||||
typescript \
|
typescript \
|
||||||
@typescript-eslint \
|
@typescript-eslint \
|
||||||
@napi-rs
|
@napi-rs
|
||||||
|
|||||||
42
backend/package-lock.json
generated
42
backend/package-lock.json
generated
@@ -18,12 +18,12 @@
|
|||||||
"crypto-js": "~4.2.0",
|
"crypto-js": "~4.2.0",
|
||||||
"express": "~4.19.2",
|
"express": "~4.19.2",
|
||||||
"maxmind": "~4.3.11",
|
"maxmind": "~4.3.11",
|
||||||
"mysql2": "~3.9.1",
|
"mysql2": "~3.9.4",
|
||||||
"redis": "^4.6.6",
|
"redis": "^4.6.6",
|
||||||
"rust-gbt": "file:./rust-gbt",
|
"rust-gbt": "file:./rust-gbt",
|
||||||
"socks-proxy-agent": "~7.0.0",
|
"socks-proxy-agent": "~7.0.0",
|
||||||
"typescript": "~4.9.3",
|
"typescript": "~4.9.3",
|
||||||
"ws": "~8.13.0"
|
"ws": "~8.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.0",
|
||||||
"@types/ws": "~8.5.5",
|
"@types/ws": "~8.5.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||||
"@typescript-eslint/parser": "^5.55.0",
|
"@typescript-eslint/parser": "^5.55.0",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
@@ -1858,9 +1858,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.5.5",
|
"version": "8.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
|
||||||
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
|
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@@ -6197,9 +6197,9 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/mysql2": {
|
"node_modules/mysql2": {
|
||||||
"version": "3.9.1",
|
"version": "3.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.4.tgz",
|
||||||
"integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==",
|
"integrity": "sha512-OEESQuwxMza803knC1YSt7NMuc1BrK9j7gZhCSs2WAyxr1vfiI7QLaLOKTh5c9SWGz98qVyQUbK8/WckevNQhg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
"generate-function": "^2.3.1",
|
"generate-function": "^2.3.1",
|
||||||
@@ -7690,9 +7690,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.13.0",
|
"version": "8.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@@ -9198,9 +9198,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/ws": {
|
"@types/ws": {
|
||||||
"version": "8.5.5",
|
"version": "8.5.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
|
||||||
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
|
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@@ -12382,9 +12382,9 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"mysql2": {
|
"mysql2": {
|
||||||
"version": "3.9.1",
|
"version": "3.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.4.tgz",
|
||||||
"integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==",
|
"integrity": "sha512-OEESQuwxMza803knC1YSt7NMuc1BrK9j7gZhCSs2WAyxr1vfiI7QLaLOKTh5c9SWGz98qVyQUbK8/WckevNQhg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
"generate-function": "^2.3.1",
|
"generate-function": "^2.3.1",
|
||||||
@@ -13424,9 +13424,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "8.13.0",
|
"version": "8.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
|
|||||||
@@ -47,12 +47,12 @@
|
|||||||
"crypto-js": "~4.2.0",
|
"crypto-js": "~4.2.0",
|
||||||
"express": "~4.19.2",
|
"express": "~4.19.2",
|
||||||
"maxmind": "~4.3.11",
|
"maxmind": "~4.3.11",
|
||||||
"mysql2": "~3.9.1",
|
"mysql2": "~3.9.4",
|
||||||
"rust-gbt": "file:./rust-gbt",
|
"rust-gbt": "file:./rust-gbt",
|
||||||
"redis": "^4.6.6",
|
"redis": "^4.6.6",
|
||||||
"socks-proxy-agent": "~7.0.0",
|
"socks-proxy-agent": "~7.0.0",
|
||||||
"typescript": "~4.9.3",
|
"typescript": "~4.9.3",
|
||||||
"ws": "~8.13.0"
|
"ws": "~8.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.0",
|
||||||
"@types/ws": "~8.5.5",
|
"@types/ws": "~8.5.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||||
"@typescript-eslint/parser": "^5.55.0",
|
"@typescript-eslint/parser": "^5.55.0",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"BLOCKS_SUMMARIES_INDEXING": true,
|
"BLOCKS_SUMMARIES_INDEXING": true,
|
||||||
"GOGGLES_INDEXING": false,
|
"GOGGLES_INDEXING": false,
|
||||||
"HTTP_PORT": 1,
|
"HTTP_PORT": 1,
|
||||||
|
"UNIX_SOCKET_PATH": "/mempool/socket/mempool-bitcoin-mainnet",
|
||||||
"SPAWN_CLUSTER_PROCS": 2,
|
"SPAWN_CLUSTER_PROCS": 2,
|
||||||
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
||||||
"AUTOMATIC_BLOCK_REINDEXING": false,
|
"AUTOMATIC_BLOCK_REINDEXING": false,
|
||||||
@@ -143,6 +144,7 @@
|
|||||||
},
|
},
|
||||||
"FIAT_PRICE": {
|
"FIAT_PRICE": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
"PAID": false,
|
||||||
"API_KEY": "__MEMPOOL_CURRENCY_API_KEY__"
|
"API_KEY": "__MEMPOOL_CURRENCY_API_KEY__"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ describe('Mempool Backend Config', () => {
|
|||||||
BLOCKS_SUMMARIES_INDEXING: false,
|
BLOCKS_SUMMARIES_INDEXING: false,
|
||||||
GOGGLES_INDEXING: false,
|
GOGGLES_INDEXING: false,
|
||||||
HTTP_PORT: 8999,
|
HTTP_PORT: 8999,
|
||||||
|
UNIX_SOCKET_PATH: '',
|
||||||
SPAWN_CLUSTER_PROCS: 0,
|
SPAWN_CLUSTER_PROCS: 0,
|
||||||
API_URL_PREFIX: '/api/v1/',
|
API_URL_PREFIX: '/api/v1/',
|
||||||
AUTOMATIC_BLOCK_REINDEXING: false,
|
AUTOMATIC_BLOCK_REINDEXING: false,
|
||||||
@@ -150,6 +151,7 @@ describe('Mempool Backend Config', () => {
|
|||||||
|
|
||||||
expect(config.FIAT_PRICE).toStrictEqual({
|
expect(config.FIAT_PRICE).toStrictEqual({
|
||||||
ENABLED: true,
|
ENABLED: true,
|
||||||
|
PAID: false,
|
||||||
API_KEY: '',
|
API_KEY: '',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
87
backend/src/api/about.routes.ts
Normal file
87
backend/src/api/about.routes.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { Application } from "express";
|
||||||
|
import config from "../config";
|
||||||
|
import axios from "axios";
|
||||||
|
import logger from "../logger";
|
||||||
|
|
||||||
|
class AboutRoutes {
|
||||||
|
public initRoutes(app: Application) {
|
||||||
|
app
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations`, { responseType: 'stream', timeout: 10000 });
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, {
|
||||||
|
responseType: 'stream', timeout: 10000
|
||||||
|
});
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors`, { responseType: 'stream', timeout: 10000 });
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, {
|
||||||
|
responseType: 'stream', timeout: 10000
|
||||||
|
});
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators`, { responseType: 'stream', timeout: 10000 });
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, {
|
||||||
|
responseType: 'stream', timeout: 10000
|
||||||
|
});
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'services/sponsors', async (req, res) => {
|
||||||
|
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Unable to fetch sponsors from ${url}. ${e}`, 'About Page');
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'services/account/images/:username', async (req, res) => {
|
||||||
|
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Unable to fetch sponsor profile image from ${url}. ${e}`, 'About Page');
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AboutRoutes();
|
||||||
69
backend/src/api/acceleration/acceleration.routes.ts
Normal file
69
backend/src/api/acceleration/acceleration.routes.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { Application, Request, Response } from 'express';
|
||||||
|
import config from '../../config';
|
||||||
|
import axios from 'axios';
|
||||||
|
import logger from '../../logger';
|
||||||
|
import mempool from '../mempool';
|
||||||
|
import AccelerationRepository from '../../repositories/AccelerationRepository';
|
||||||
|
|
||||||
|
class AccelerationRoutes {
|
||||||
|
private tag = 'Accelerator';
|
||||||
|
|
||||||
|
public initRoutes(app: Application): void {
|
||||||
|
app
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations', this.$getAcceleratorAccelerations.bind(this))
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this))
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history/aggregated', this.$getAcceleratorAccelerationsHistoryAggregated.bind(this))
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/stats', this.$getAcceleratorAccelerationsStats.bind(this))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async $getAcceleratorAccelerations(req: Request, res: Response): Promise<void> {
|
||||||
|
const accelerations = mempool.getAccelerations();
|
||||||
|
res.status(200).send(Object.values(accelerations));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async $getAcceleratorAccelerationsHistory(req: Request, res: Response): Promise<void> {
|
||||||
|
const history = await AccelerationRepository.$getAccelerationInfo(null, req.query.blockHeight ? parseInt(req.query.blockHeight as string, 10) : null);
|
||||||
|
res.status(200).send(history.map(accel => ({
|
||||||
|
txid: accel.txid,
|
||||||
|
added: accel.added,
|
||||||
|
status: 'completed',
|
||||||
|
effectiveFee: accel.effective_fee,
|
||||||
|
effectiveVsize: accel.effective_vsize,
|
||||||
|
boostRate: accel.boost_rate,
|
||||||
|
boostCost: accel.boost_cost,
|
||||||
|
blockHeight: accel.height,
|
||||||
|
pools: [accel.pool],
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response): Promise<void> {
|
||||||
|
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
|
||||||
|
for (const key in response.headers) {
|
||||||
|
res.setHeader(key, response.headers[key]);
|
||||||
|
}
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Unable to get aggregated acceleration history from ${url} in $getAcceleratorAccelerationsHistoryAggregated(), ${e}`, this.tag);
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async $getAcceleratorAccelerationsStats(req: Request, res: Response): Promise<void> {
|
||||||
|
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
|
||||||
|
for (const key in response.headers) {
|
||||||
|
res.setHeader(key, response.headers[key]);
|
||||||
|
}
|
||||||
|
response.data.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Unable to get acceleration stats from ${url} in $getAcceleratorAccelerationsStats(), ${e}`, this.tag);
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AccelerationRoutes();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import logger from '../logger';
|
import logger from '../../logger';
|
||||||
import { MempoolTransactionExtended } from '../mempool.interfaces';
|
import { MempoolTransactionExtended } from '../../mempool.interfaces';
|
||||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
|
||||||
|
|
||||||
const BLOCK_WEIGHT_UNITS = 4_000_000;
|
const BLOCK_WEIGHT_UNITS = 4_000_000;
|
||||||
const BLOCK_SIGOPS = 80_000;
|
const BLOCK_SIGOPS = 80_000;
|
||||||
@@ -75,10 +75,6 @@ class Audit {
|
|||||||
let failures = 0;
|
let failures = 0;
|
||||||
let blockIndex = 1;
|
let blockIndex = 1;
|
||||||
while (projectedBlocks[blockIndex] && failures < 500) {
|
while (projectedBlocks[blockIndex] && failures < 500) {
|
||||||
if (index >= projectedBlocks[blockIndex].transactionIds.length) {
|
|
||||||
index = 0;
|
|
||||||
blockIndex++;
|
|
||||||
}
|
|
||||||
const txid = projectedBlocks[blockIndex].transactionIds[index];
|
const txid = projectedBlocks[blockIndex].transactionIds[index];
|
||||||
const tx = mempool[txid];
|
const tx = mempool[txid];
|
||||||
if (tx) {
|
if (tx) {
|
||||||
@@ -102,6 +98,10 @@ class Audit {
|
|||||||
logger.warn('projected transaction missing from mempool cache');
|
logger.warn('projected transaction missing from mempool cache');
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
|
if (index >= projectedBlocks[blockIndex].transactionIds.length) {
|
||||||
|
index = 0;
|
||||||
|
blockIndex++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark unexpected transactions in the mined block as 'added'
|
// mark unexpected transactions in the mined block as 'added'
|
||||||
|
|||||||
@@ -37,60 +37,6 @@ class BitcoinRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'replacements', this.getRbfReplacements)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'replacements', this.getRbfReplacements)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fullrbf/replacements', this.getFullRbfReplacements)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'fullrbf/replacements', this.getFullRbfReplacements)
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations`, { responseType: 'stream', timeout: 10000 });
|
|
||||||
response.data.pipe(res);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).end();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, {
|
|
||||||
responseType: 'stream', timeout: 10000
|
|
||||||
});
|
|
||||||
response.data.pipe(res);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).end();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors`, { responseType: 'stream', timeout: 10000 });
|
|
||||||
response.data.pipe(res);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).end();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, {
|
|
||||||
responseType: 'stream', timeout: 10000
|
|
||||||
});
|
|
||||||
response.data.pipe(res);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).end();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators`, { responseType: 'stream', timeout: 10000 });
|
|
||||||
response.data.pipe(res);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).end();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, {
|
|
||||||
responseType: 'stream', timeout: 10000
|
|
||||||
});
|
|
||||||
response.data.pipe(res);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).end();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks', this.getBlocks.bind(this))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks', this.getBlocks.bind(this))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', this.getBlocks.bind(this))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', this.getBlocks.bind(this))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', this.getBlock)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', this.getBlock)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import websocketHandler from './websocket-handler';
|
|||||||
import redisCache from './redis-cache';
|
import redisCache from './redis-cache';
|
||||||
import rbfCache from './rbf-cache';
|
import rbfCache from './rbf-cache';
|
||||||
import { calcBitsDifference } from './difficulty-adjustment';
|
import { calcBitsDifference } from './difficulty-adjustment';
|
||||||
|
import AccelerationRepository from '../repositories/AccelerationRepository';
|
||||||
|
|
||||||
class Blocks {
|
class Blocks {
|
||||||
private blocks: BlockExtended[] = [];
|
private blocks: BlockExtended[] = [];
|
||||||
@@ -872,6 +873,7 @@ class Blocks {
|
|||||||
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
||||||
await HashratesRepository.$deleteLastEntries();
|
await HashratesRepository.$deleteLastEntries();
|
||||||
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
|
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
|
||||||
|
await AccelerationRepository.$deleteAccelerationsFrom(lastBlock.height - 10);
|
||||||
this.blocks = this.blocks.slice(0, -10);
|
this.blocks = this.blocks.slice(0, -10);
|
||||||
this.updateTimerProgress(timer, `rolled back chain divergence from ${this.currentBlockHeight}`);
|
this.updateTimerProgress(timer, `rolled back chain divergence from ${this.currentBlockHeight}`);
|
||||||
for (let i = 10; i >= 0; --i) {
|
for (let i = 10; i >= 0; --i) {
|
||||||
@@ -974,6 +976,9 @@ class Blocks {
|
|||||||
if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
|
if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
|
||||||
this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
|
this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
|
||||||
}
|
}
|
||||||
|
blockSummary.transactions.forEach(tx => {
|
||||||
|
delete tx.acc;
|
||||||
|
});
|
||||||
this.blockSummaries.push(blockSummary);
|
this.blockSummaries.push(blockSummary);
|
||||||
if (this.blockSummaries.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
|
if (this.blockSummaries.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
|
||||||
this.blockSummaries = this.blockSummaries.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
|
this.blockSummaries = this.blockSummaries.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
|
||||||
@@ -1117,6 +1122,7 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
|
time: tx.firstSeen,
|
||||||
fee: tx.fee || 0,
|
fee: tx.fee || 0,
|
||||||
vsize: tx.vsize,
|
vsize: tx.vsize,
|
||||||
value: Math.round(tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0)),
|
value: Math.round(tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0)),
|
||||||
|
|||||||
@@ -373,6 +373,21 @@ export class Common {
|
|||||||
].includes(pubkey);
|
].includes(pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isInscription(vin, flags): bigint {
|
||||||
|
// in taproot, if the last witness item begins with 0x50, it's an annex
|
||||||
|
const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50');
|
||||||
|
// script spends have more than one witness item, not counting the annex (if present)
|
||||||
|
if (vin.witness.length > (hasAnnex ? 2 : 1)) {
|
||||||
|
// the script itself is the second-to-last witness item, not counting the annex
|
||||||
|
const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - (hasAnnex ? 3 : 2)]);
|
||||||
|
// inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope
|
||||||
|
if (asm?.includes('OP_0 OP_IF')) {
|
||||||
|
flags |= TransactionFlags.inscription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
static getTransactionFlags(tx: TransactionExtended): number {
|
static getTransactionFlags(tx: TransactionExtended): number {
|
||||||
let flags = tx.flags ? BigInt(tx.flags) : 0n;
|
let flags = tx.flags ? BigInt(tx.flags) : 0n;
|
||||||
|
|
||||||
@@ -409,30 +424,31 @@ export class Common {
|
|||||||
if (vin.sequence < 0xfffffffe) {
|
if (vin.sequence < 0xfffffffe) {
|
||||||
rbf = true;
|
rbf = true;
|
||||||
}
|
}
|
||||||
switch (vin.prevout?.scriptpubkey_type) {
|
if (vin.prevout?.scriptpubkey_type) {
|
||||||
case 'p2pk': flags |= TransactionFlags.p2pk; break;
|
switch (vin.prevout?.scriptpubkey_type) {
|
||||||
case 'multisig': flags |= TransactionFlags.p2ms; break;
|
case 'p2pk': flags |= TransactionFlags.p2pk; break;
|
||||||
case 'p2pkh': flags |= TransactionFlags.p2pkh; break;
|
case 'multisig': flags |= TransactionFlags.p2ms; break;
|
||||||
case 'p2sh': flags |= TransactionFlags.p2sh; break;
|
case 'p2pkh': flags |= TransactionFlags.p2pkh; break;
|
||||||
case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break;
|
case 'p2sh': flags |= TransactionFlags.p2sh; break;
|
||||||
case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break;
|
case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break;
|
||||||
case 'v1_p2tr': {
|
case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break;
|
||||||
if (!vin.witness?.length) {
|
case 'v1_p2tr': {
|
||||||
throw new Error('Taproot input missing witness data');
|
if (!vin.witness?.length) {
|
||||||
}
|
throw new Error('Taproot input missing witness data');
|
||||||
flags |= TransactionFlags.p2tr;
|
|
||||||
// in taproot, if the last witness item begins with 0x50, it's an annex
|
|
||||||
const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50');
|
|
||||||
// script spends have more than one witness item, not counting the annex (if present)
|
|
||||||
if (vin.witness.length > (hasAnnex ? 2 : 1)) {
|
|
||||||
// the script itself is the second-to-last witness item, not counting the annex
|
|
||||||
const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - (hasAnnex ? 3 : 2)]);
|
|
||||||
// inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope
|
|
||||||
if (asm?.includes('OP_0 OP_IF')) {
|
|
||||||
flags |= TransactionFlags.inscription;
|
|
||||||
}
|
}
|
||||||
|
flags |= TransactionFlags.p2tr;
|
||||||
|
flags = Common.isInscription(vin, flags);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no prevouts, optimistically check witness-bearing inputs
|
||||||
|
if (vin.witness?.length >= 2) {
|
||||||
|
try {
|
||||||
|
flags = Common.isInscription(vin, flags);
|
||||||
|
} catch {
|
||||||
|
// witness script parsing will fail if this isn't really a taproot output
|
||||||
}
|
}
|
||||||
} break;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sighash flags
|
// sighash flags
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 76;
|
private static currentVersion = 79;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@@ -652,6 +652,11 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery('ALTER TABLE `prices` ADD `THB` float DEFAULT "-1"');
|
await this.$executeQuery('ALTER TABLE `prices` ADD `THB` float DEFAULT "-1"');
|
||||||
await this.$executeQuery('ALTER TABLE `prices` ADD `TRY` float DEFAULT "-1"');
|
await this.$executeQuery('ALTER TABLE `prices` ADD `TRY` float DEFAULT "-1"');
|
||||||
await this.$executeQuery('ALTER TABLE `prices` ADD `ZAR` float DEFAULT "-1"');
|
await this.$executeQuery('ALTER TABLE `prices` ADD `ZAR` float DEFAULT "-1"');
|
||||||
|
|
||||||
|
await this.$executeQuery('TRUNCATE hashrates');
|
||||||
|
await this.$executeQuery('TRUNCATE difficulty_adjustments');
|
||||||
|
await this.$executeQuery(`UPDATE state SET string = NULL WHERE name = 'pools_json_sha'`);
|
||||||
|
|
||||||
await this.updateToSchemaVersion(75);
|
await this.updateToSchemaVersion(75);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,6 +664,29 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"');
|
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"');
|
||||||
await this.updateToSchemaVersion(76);
|
await this.updateToSchemaVersion(76);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 77 && config.MEMPOOL.NETWORK === 'mainnet') {
|
||||||
|
await this.$executeQuery('ALTER TABLE `accelerations` ADD requested datetime DEFAULT NULL');
|
||||||
|
await this.updateToSchemaVersion(77);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 78) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `prices` CHANGE `time` `time` datetime NOT NULL');
|
||||||
|
await this.updateToSchemaVersion(78);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 79 && config.MEMPOOL.NETWORK === 'liquid') {
|
||||||
|
await this.$executeQuery('TRUNCATE TABLE elements_pegs');
|
||||||
|
await this.$executeQuery('TRUNCATE TABLE federation_txos');
|
||||||
|
await this.$executeQuery('SET FOREIGN_KEY_CHECKS = 0');
|
||||||
|
await this.$executeQuery('TRUNCATE TABLE federation_addresses');
|
||||||
|
await this.$executeQuery('SET FOREIGN_KEY_CHECKS = 1');
|
||||||
|
await this.$executeQuery(`INSERT INTO federation_addresses (bitcoinaddress) VALUES ('bc1qxvay4an52gcghxq5lavact7r6qe9l4laedsazz8fj2ee2cy47tlqff4aj4')`); // Federation change address
|
||||||
|
await this.$executeQuery(`INSERT INTO federation_addresses (bitcoinaddress) VALUES ('3EiAcrzq1cELXScc98KeCswGWZaPGceT1d')`); // Federation change address
|
||||||
|
await this.$executeQuery(`UPDATE state SET number = 0 WHERE name = 'last_elements_block';`);
|
||||||
|
await this.$executeQuery(`UPDATE state SET number = 0 WHERE name = 'last_bitcoin_block_audit';`);
|
||||||
|
await this.updateToSchemaVersion(79);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -713,7 +713,9 @@ class NodesApi {
|
|||||||
* Update node sockets
|
* Update node sockets
|
||||||
*/
|
*/
|
||||||
public async $updateNodeSockets(publicKey: string, sockets: {network: string; addr: string}[]): Promise<void> {
|
public async $updateNodeSockets(publicKey: string, sockets: {network: string; addr: string}[]): Promise<void> {
|
||||||
const formattedSockets = (sockets.map(a => a.addr).join(',')) ?? '';
|
const uniqueAddr = [...new Set(sockets.map(a => a.addr))];
|
||||||
|
|
||||||
|
const formattedSockets = (uniqueAddr.join(',')) ?? '';
|
||||||
try {
|
try {
|
||||||
await DB.query(`UPDATE nodes SET sockets = ? WHERE public_key = ?`, [formattedSockets, publicKey]);
|
await DB.query(`UPDATE nodes SET sockets = ? WHERE public_key = ?`, [formattedSockets, publicKey]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ class NodesRoutes {
|
|||||||
'032850492ee61a5f7006a2fda6925e4b4ec3782f2b6de2ff0e439ef5a38c3b2470',
|
'032850492ee61a5f7006a2fda6925e4b4ec3782f2b6de2ff0e439ef5a38c3b2470',
|
||||||
'022c80bace98831c44c32fb69755f2b353434e0ee9e7fbda29507f7ef8abea1421',
|
'022c80bace98831c44c32fb69755f2b353434e0ee9e7fbda29507f7ef8abea1421',
|
||||||
'02c3559c833e6f99f9ca05fe503e0b4e7524dea9121344edfd3e811101e0c28680',
|
'02c3559c833e6f99f9ca05fe503e0b4e7524dea9121344edfd3e811101e0c28680',
|
||||||
|
'02b36a324fa2dd3af2a63ac65f241907882829bed5002b4e14171d25c219e0d470',
|
||||||
|
'0231b6e8f21f9f6c057f6bf8a812f79e396ee16a66ece91939a1576ce9fb9e87a5',
|
||||||
|
'034b6aac206bffcbd651b7ead1ab8a0991c945dfafe19ff27dcdeadc6843ebd15c',
|
||||||
|
'039c065f7e344acd969ebdd4a94550915b6f24e8782ae2be540bb96c8a4fcfb86b',
|
||||||
|
'03d9f9f4803fc75920f14dd13d83fbecc53229a65d4ee4cd2d86fdf211f7337576',
|
||||||
|
'0357fe48c4dece744f70865eda66e396aab5d05e09e1145cd3b7da83f11446d4cf',
|
||||||
|
'02bca4d642eda631f2c8659758e2a2868e518b93503f2bfcd767749c6530a10679',
|
||||||
|
'03f32c99c0bb9f62dae53671d1d300565773455248f34134cc02779b881561174e',
|
||||||
'032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b',
|
'032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b',
|
||||||
'025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7',
|
'025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7',
|
||||||
'0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55',
|
'0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55',
|
||||||
@@ -60,6 +68,14 @@ class NodesRoutes {
|
|||||||
'039c14fdec2d958e3d14cebf657451bbd9e039196615785e82c917f274e3fb2205',
|
'039c14fdec2d958e3d14cebf657451bbd9e039196615785e82c917f274e3fb2205',
|
||||||
'033589bbcb233ffc416cefd5437c7f37e9d7cb7942d405e39e72c4c846d9b37f18',
|
'033589bbcb233ffc416cefd5437c7f37e9d7cb7942d405e39e72c4c846d9b37f18',
|
||||||
'029293110441c6e2eacb57e1255bf6ef05c41a6a676fe474922d33c19f98a7d584',
|
'029293110441c6e2eacb57e1255bf6ef05c41a6a676fe474922d33c19f98a7d584',
|
||||||
|
'038eb09bed4532ff36d12acc1279f55cbe8d95212d19f809e057bb50de00051fba',
|
||||||
|
'027b7c0278366a0268e8bd0072b14539f6cb455a7bd588ae22d888bed541f65311',
|
||||||
|
'02f4dd78f6eda8838029b2cdbaaea6e875e2fa373cd348ee41a7c1bb177d3fca66',
|
||||||
|
'036b3fb692da214a3edaac5b67903b958f5ccd8712e09aa61b67ea7acfd94b40c2',
|
||||||
|
'023bc8915d308e0b65f8de6867f95960141372436fce3edad5cec3f364d6ac948f',
|
||||||
|
'0341690503ef21d0e203dddd9e62646380d0dfc32c499e055e7f698b9064d1c736',
|
||||||
|
'0355d573805c018a37a5b2288378d70e9b5b438f7394abd6f467cb9b47c90eeb93',
|
||||||
|
'0361aa68deb561a8b47b41165848edcccb98a1b56a5ea922d9d5b30a09bb7282ea',
|
||||||
'0235ad0b56ed8c42c4354444c24e971c05e769ec0b5fb0ccea42880095dc02ea2c',
|
'0235ad0b56ed8c42c4354444c24e971c05e769ec0b5fb0ccea42880095dc02ea2c',
|
||||||
'029700819a37afea630f80e6cc461f3fd3c4ace2598a21cfbbe64d1c78d0ee69a5',
|
'029700819a37afea630f80e6cc461f3fd3c4ace2598a21cfbbe64d1c78d0ee69a5',
|
||||||
'02c2d8b2dbf87c7894af2f1d321290e2fe6db5446cd35323987cee98f06e2e0075',
|
'02c2d8b2dbf87c7894af2f1d321290e2fe6db5446cd35323987cee98f06e2e0075',
|
||||||
@@ -76,6 +92,14 @@ class NodesRoutes {
|
|||||||
'0243348cb3741cfe2d8485fa8375c29c7bc7cbb67577c363cb6987a5e5fd0052cc',
|
'0243348cb3741cfe2d8485fa8375c29c7bc7cbb67577c363cb6987a5e5fd0052cc',
|
||||||
'02cb73e631af44bee600d80f8488a9194c9dc5c7590e575c421a070d1be05bc8e9',
|
'02cb73e631af44bee600d80f8488a9194c9dc5c7590e575c421a070d1be05bc8e9',
|
||||||
'0306f55ee631aa1e2cd4d9b2bfcbc14404faec5c541cef8b2e6f779061029d09c4',
|
'0306f55ee631aa1e2cd4d9b2bfcbc14404faec5c541cef8b2e6f779061029d09c4',
|
||||||
|
'030bbbd8495561a894e301fe6ba5b22f8941fc661cc0e673e0206158231d8ac130',
|
||||||
|
'03ee1f08e516ed083475f39c6cae4fa1eec686d004d2f105218269e27d7f2da5a4',
|
||||||
|
'028c378b998f476ed22d6815c170dd2a3388a43fdf791a7cff70b9997349b8447a',
|
||||||
|
'036f19f044d19cb1b04f14d91b6e7e5443ce337217a8c14d43861f3e86dd07bd7f',
|
||||||
|
'03058d61869e8b88436493648b2e3e530627edf5a0b253c285cd565c1477a5c237',
|
||||||
|
'0279dfedc87b47a941f1797f2c422c03aa3108914ea6b519d76537d60860535a9a',
|
||||||
|
'0353486b8016761e58ec8aee7305ee58d5dc66b55ef5bd8cbaf49508f66d52d62e',
|
||||||
|
'03df5db8eccfabcae47ff15553cfdecb2d3f56979f43a0c3578f28d056b5e35104',
|
||||||
'03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956',
|
'03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956',
|
||||||
'033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de',
|
'033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de',
|
||||||
'02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781',
|
'02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781',
|
||||||
@@ -88,6 +112,14 @@ class NodesRoutes {
|
|||||||
'038310e3a786340f2bd7770704c7ccfe560fd163d9a1c99d67894597419d12cbf7',
|
'038310e3a786340f2bd7770704c7ccfe560fd163d9a1c99d67894597419d12cbf7',
|
||||||
'03e5e9d879b72c7d67ecd483bae023bd33e695bb32b981a4021260f7b9d62bc761',
|
'03e5e9d879b72c7d67ecd483bae023bd33e695bb32b981a4021260f7b9d62bc761',
|
||||||
'028d16e1a0ace4c0c0a421536d8d32ce484dfe6e2f726b7b0e7c30f12a195f8cc7',
|
'028d16e1a0ace4c0c0a421536d8d32ce484dfe6e2f726b7b0e7c30f12a195f8cc7',
|
||||||
|
'0326cf9a4ca67a5b9cdffae57293dbd6f7c5113b93010dc6f6fe4af3afde1a1739',
|
||||||
|
'034867e16f62cebb8c2c2c22b91117c173bbece9c8a1e5bd001374a3699551cd8f',
|
||||||
|
'038dfb1f1b637a8c27e342ffc6f9feca20e0b47be3244e09ae78df4998e2ae83b9',
|
||||||
|
'03cb1cea3394d973355c11bc61c2f689f9d3e1c3db60d205f27770f5ad83200f77',
|
||||||
|
'03535447b592cbdb153189b3e06a455452b1011380cb3e6511a31090c15d8efc9f',
|
||||||
|
'028e90e9984d262ebfa3c23fb3f335a2ae061a0bdedee03f45f72b438d9e7d2ce3',
|
||||||
|
'03ee0176289dc4a6111fa5ef22eed5273758c420fbe58cc1d2d76def75dd7e640c',
|
||||||
|
'0370b2cd9f0eaf436d5c25c93fb39210d8cc06b31f688fc2f54418aabe394aed79',
|
||||||
'02ff690d06c187ab994bf83c5a2114fe5bf50112c2c817af0f788f736be9fa2070',
|
'02ff690d06c187ab994bf83c5a2114fe5bf50112c2c817af0f788f736be9fa2070',
|
||||||
'02a9f570c51a2526a5ee85802e88f9281bed771eb66a0c8a7d898430dd5d0eae45',
|
'02a9f570c51a2526a5ee85802e88f9281bed771eb66a0c8a7d898430dd5d0eae45',
|
||||||
'038c3de773255d3bd7a50e31e58d423baac5c90826a74d75e64b74c95475de1097',
|
'038c3de773255d3bd7a50e31e58d423baac5c90826a74d75e64b74c95475de1097',
|
||||||
@@ -104,6 +136,14 @@ class NodesRoutes {
|
|||||||
'03229ab4b7f692753e094b93df90530150680f86b535b5183b0cffd75b3df583fc',
|
'03229ab4b7f692753e094b93df90530150680f86b535b5183b0cffd75b3df583fc',
|
||||||
'03a696eb7acde991c1be97a58a9daef416659539ae462b897f5e9ae361f990228e',
|
'03a696eb7acde991c1be97a58a9daef416659539ae462b897f5e9ae361f990228e',
|
||||||
'0248bf26cf3a63ab8870f34dc0ec9e6c8c6288cdba96ba3f026f34ec0f13ac4055',
|
'0248bf26cf3a63ab8870f34dc0ec9e6c8c6288cdba96ba3f026f34ec0f13ac4055',
|
||||||
|
'021b28ecdd782fd909705d6be354db268977b1a2ac5a5275186fc19e08bb8fca93',
|
||||||
|
'031bec1fbd8eb7fe94d2bda108c9c3cc8c22ecfc1c3a5c11d36f5881b01b4a81a6',
|
||||||
|
'03879c4f827a3188574d5757e002f574265a966d70aea942169785b31369b067d5',
|
||||||
|
'0228d4b5a4fd73a03967b76f8b8cb37b9d0b6e7039126a9397bb732c15bed78e9b',
|
||||||
|
'03f58dbb629f4427f5a1dbc02e6a7ec79345fdf13a0e4163d4f3b7aea2539cf095',
|
||||||
|
'021cdcb8123aa670cdfc9f43909dbb297363c093883409e9e7fc82e7267f7c72bd',
|
||||||
|
'02f2aa2c2b7b432a70dc4d0b04afa19d48715ed3b90594d49c1c8744f2e9ebb030',
|
||||||
|
'03709a02fb3ab4857689a8ea0bd489a6ab6f56f8a397be578bc6d5ad22efbe3756',
|
||||||
'03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61',
|
'03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61',
|
||||||
'03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437',
|
'03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437',
|
||||||
'03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144',
|
'03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144',
|
||||||
@@ -116,6 +156,14 @@ class NodesRoutes {
|
|||||||
'02b6b1640fe029e304c216951af9fbefdb23b0bdc9baaf327540d31b6107841fdf',
|
'02b6b1640fe029e304c216951af9fbefdb23b0bdc9baaf327540d31b6107841fdf',
|
||||||
'03694289827203a5b3156d753071ddd5bf92e371f5a462943f9555eef6d2d6606c',
|
'03694289827203a5b3156d753071ddd5bf92e371f5a462943f9555eef6d2d6606c',
|
||||||
'0283d850db7c3e8ea7cc9c4abc7afaab12bbdf72b677dcba1d608350d2537d7d43',
|
'0283d850db7c3e8ea7cc9c4abc7afaab12bbdf72b677dcba1d608350d2537d7d43',
|
||||||
|
'03b4dda7878d3b7b71ecd6d4738322c7f9a9c1fb583374d2724f4ccc4947f37570',
|
||||||
|
'0279a35f05b5acf159429549e56fd426685c4fec191431c58738968bbc77a39f25',
|
||||||
|
'03cb102d796ddcf08610cd03fae8b7a1df69ff48e9e8a152af315f9edf71762eb8',
|
||||||
|
'036b89526f4d5ac4c317f4fd23cb9f8e4ad844498bc7950a41114d060101d995d4',
|
||||||
|
'0313eade145959d7036db009fd5b0bf1947a739c7c3c790b491ec9161b94e6ad1e',
|
||||||
|
'02b670ca4c4bb2c5ea89c3b691da98a194cfc48fcd5c072df02a20290bddd60610',
|
||||||
|
'02a9196d5e08598211397a83cf013a5962b84bd61198abfdd204dff987e54f7a0d',
|
||||||
|
'036d015cd2f486fb38348182980b7e596e6c9733873102ea126fed7b4152be03b8',
|
||||||
'02521287789f851268a39c9eccc9d6180d2c614315b583c9e6ae0addbd6d79df06',
|
'02521287789f851268a39c9eccc9d6180d2c614315b583c9e6ae0addbd6d79df06',
|
||||||
'0258c2a7b7f8af2585b4411b1ec945f70988f30412bb1df179de941f14d0b1bc3e',
|
'0258c2a7b7f8af2585b4411b1ec945f70988f30412bb1df179de941f14d0b1bc3e',
|
||||||
'03c3389ff1a896f84d921ed01a19fc99c6724ce8dc4b960cd3b7b2362b62cd60d7',
|
'03c3389ff1a896f84d921ed01a19fc99c6724ce8dc4b960cd3b7b2362b62cd60d7',
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ class ElementsParser {
|
|||||||
|
|
||||||
for (const utxo of unspentAsTip) {
|
for (const utxo of unspentAsTip) {
|
||||||
if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block
|
if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block
|
||||||
await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, block.time, utxo.txid, utxo.txindex]);
|
await DB.query(`UPDATE federation_txos SET lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, block.time, utxo.txid, utxo.txindex]);
|
||||||
} else if (utxo.expiredAt === 0 && confirmedTip >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring before the tip: we need to keep track of it
|
} else if (utxo.expiredAt === 0 && confirmedTip >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring before the tip: we need to keep track of it
|
||||||
await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]);
|
await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ class MempoolBlocks {
|
|||||||
if (txid in mempool) {
|
if (txid in mempool) {
|
||||||
mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
|
mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
|
||||||
mempool[txid].effectiveFeePerVsize = rate;
|
mempool[txid].effectiveFeePerVsize = rate;
|
||||||
mempool[txid].cpfpChecked = false;
|
mempool[txid].cpfpChecked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import axios from 'axios';
|
|||||||
|
|
||||||
export interface Acceleration {
|
export interface Acceleration {
|
||||||
txid: string,
|
txid: string,
|
||||||
|
added: number,
|
||||||
|
effectiveVsize: number,
|
||||||
|
effectiveFee: number,
|
||||||
feeDelta: number,
|
feeDelta: number,
|
||||||
pools: number[],
|
pools: number[],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const wantable = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
class WebsocketHandler {
|
class WebsocketHandler {
|
||||||
private wss: WebSocket.Server | undefined;
|
private webSocketServers: WebSocket.Server[] = [];
|
||||||
private extraInitProperties = {};
|
private extraInitProperties = {};
|
||||||
|
|
||||||
private numClients = 0;
|
private numClients = 0;
|
||||||
@@ -54,11 +54,12 @@ class WebsocketHandler {
|
|||||||
private socketData: { [key: string]: string } = {};
|
private socketData: { [key: string]: string } = {};
|
||||||
private serializedInitData: string = '{}';
|
private serializedInitData: string = '{}';
|
||||||
private lastRbfSummary: ReplacementInfo[] | null = null;
|
private lastRbfSummary: ReplacementInfo[] | null = null;
|
||||||
|
private mempoolSequence: number = 0;
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
setWebsocketServer(wss: WebSocket.Server) {
|
addWebsocketServer(wss: WebSocket.Server) {
|
||||||
this.wss = wss;
|
this.webSocketServers.push(wss);
|
||||||
}
|
}
|
||||||
|
|
||||||
setExtraInitData(property: string, value: any) {
|
setExtraInitData(property: string, value: any) {
|
||||||
@@ -102,11 +103,13 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupConnectionHandling() {
|
setupConnectionHandling() {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wss.on('connection', (client: WebSocket, req) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.on('connection', (client: WebSocket, req) => {
|
||||||
this.numConnected++;
|
this.numConnected++;
|
||||||
client['remoteAddress'] = req.headers['x-forwarded-for'] || req.socket?.remoteAddress || 'unknown';
|
client['remoteAddress'] = req.headers['x-forwarded-for'] || req.socket?.remoteAddress || 'unknown';
|
||||||
client.on('error', (e) => {
|
client.on('error', (e) => {
|
||||||
@@ -315,6 +318,7 @@ class WebsocketHandler {
|
|||||||
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
||||||
response['projected-block-transactions'] = JSON.stringify({
|
response['projected-block-transactions'] = JSON.stringify({
|
||||||
index: index,
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
|
blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -369,14 +373,17 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewDonation(id: string) {
|
handleNewDonation(id: string) {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -384,43 +391,50 @@ class WebsocketHandler {
|
|||||||
client.send(JSON.stringify({ donationConfirmed: true }));
|
client.send(JSON.stringify({ donationConfirmed: true }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadingChanged(indicators: ILoadingIndicators) {
|
handleLoadingChanged(indicators: ILoadingIndicators) {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSocketDataFields({ 'loadingIndicators': indicators });
|
this.updateSocketDataFields({ 'loadingIndicators': indicators });
|
||||||
|
|
||||||
const response = JSON.stringify({ loadingIndicators: indicators });
|
const response = JSON.stringify({ loadingIndicators: indicators });
|
||||||
this.wss.clients.forEach((client) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.send(response);
|
client.send(response);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewConversionRates(conversionRates: ApiPrice) {
|
handleNewConversionRates(conversionRates: ApiPrice) {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSocketDataFields({ 'conversions': conversionRates });
|
this.updateSocketDataFields({ 'conversions': conversionRates });
|
||||||
|
|
||||||
const response = JSON.stringify({ conversions: conversionRates });
|
const response = JSON.stringify({ conversions: conversionRates });
|
||||||
this.wss.clients.forEach((client) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.send(response);
|
client.send(response);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewStatistic(stats: OptimizedStatistic) {
|
handleNewStatistic(stats: OptimizedStatistic) {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.printLogs();
|
this.printLogs();
|
||||||
@@ -429,7 +443,9 @@ class WebsocketHandler {
|
|||||||
'live-2h-chart': stats
|
'live-2h-chart': stats
|
||||||
});
|
});
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -440,11 +456,12 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
client.send(response);
|
client.send(response);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReorg(): void {
|
handleReorg(): void {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||||
@@ -455,7 +472,9 @@ class WebsocketHandler {
|
|||||||
'da': da?.previousTime ? da : undefined,
|
'da': da?.previousTime ? da : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -473,13 +492,14 @@ class WebsocketHandler {
|
|||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number,
|
async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number,
|
||||||
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[],
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[],
|
||||||
candidates?: GbtCandidates): Promise<void> {
|
candidates?: GbtCandidates): Promise<void> {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.printLogs();
|
this.printLogs();
|
||||||
@@ -552,7 +572,9 @@ class WebsocketHandler {
|
|||||||
// pre-compute new tracked outspends
|
// pre-compute new tracked outspends
|
||||||
const outspendCache: { [txid: string]: { [vout: number]: { vin: number, txid: string } } } = {};
|
const outspendCache: { [txid: string]: { [vout: number]: { vin: number, txid: string } } } = {};
|
||||||
const trackedTxs = new Set<string>();
|
const trackedTxs = new Set<string>();
|
||||||
this.wss.clients.forEach((client) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
trackedTxs.add(client['track-tx']);
|
trackedTxs.add(client['track-tx']);
|
||||||
}
|
}
|
||||||
@@ -562,6 +584,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (trackedTxs.size > 0) {
|
if (trackedTxs.size > 0) {
|
||||||
for (const tx of newTransactions) {
|
for (const tx of newTransactions) {
|
||||||
for (let i = 0; i < tx.vin.length; i++) {
|
for (let i = 0; i < tx.vin.length; i++) {
|
||||||
@@ -581,7 +604,13 @@ class WebsocketHandler {
|
|||||||
const addressCache = this.makeAddressCache(newTransactions);
|
const addressCache = this.makeAddressCache(newTransactions);
|
||||||
const removedAddressCache = this.makeAddressCache(deletedTransactions);
|
const removedAddressCache = this.makeAddressCache(deletedTransactions);
|
||||||
|
|
||||||
this.wss.clients.forEach(async (client) => {
|
if (memPool.isInSync()) {
|
||||||
|
this.mempoolSequence++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach(async (client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -739,7 +768,7 @@ class WebsocketHandler {
|
|||||||
accelerated: mempoolTx.acceleration || undefined,
|
accelerated: mempoolTx.acceleration || undefined,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!mempoolTx.cpfpChecked) {
|
if (!mempoolTx.cpfpChecked && !mempoolTx.acceleration) {
|
||||||
calculateCpfp(mempoolTx, newMempool);
|
calculateCpfp(mempoolTx, newMempool);
|
||||||
}
|
}
|
||||||
if (mempoolTx.cpfpDirty) {
|
if (mempoolTx.cpfpDirty) {
|
||||||
@@ -802,6 +831,7 @@ class WebsocketHandler {
|
|||||||
if (mBlockDeltas[index]) {
|
if (mBlockDeltas[index]) {
|
||||||
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
delta: mBlockDeltas[index],
|
delta: mBlockDeltas[index],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -821,11 +851,12 @@ class WebsocketHandler {
|
|||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise<void> {
|
async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise<void> {
|
||||||
if (!this.wss) {
|
if (!this.webSocketServers.length) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('No WebSocket.Server have been set');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.printLogs();
|
this.printLogs();
|
||||||
@@ -969,7 +1000,13 @@ class WebsocketHandler {
|
|||||||
return responseCache[key];
|
return responseCache[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
if (memPool.isInSync()) {
|
||||||
|
this.mempoolSequence++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1135,11 +1172,13 @@ class WebsocketHandler {
|
|||||||
if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) {
|
if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) {
|
||||||
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
blockTransactions: mBlocksWithTransactions[index].transactions.map(mempoolBlocks.compressTx),
|
blockTransactions: mBlocksWithTransactions[index].transactions.map(mempoolBlocks.compressTx),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
delta: mBlockDeltas[index],
|
delta: mBlockDeltas[index],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1150,6 +1189,7 @@ class WebsocketHandler {
|
|||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await statistics.runStatistics();
|
await statistics.runStatistics();
|
||||||
}
|
}
|
||||||
@@ -1231,13 +1271,15 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private printLogs(): void {
|
private printLogs(): void {
|
||||||
if (this.wss) {
|
if (this.webSocketServers.length) {
|
||||||
let numTxSubs = 0;
|
let numTxSubs = 0;
|
||||||
let numTxsSubs = 0;
|
let numTxsSubs = 0;
|
||||||
let numProjectedSubs = 0;
|
let numProjectedSubs = 0;
|
||||||
let numRbfSubs = 0;
|
let numRbfSubs = 0;
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
// TODO - Fix indentation after PR is merged
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
numTxSubs++;
|
numTxSubs++;
|
||||||
}
|
}
|
||||||
@@ -1251,8 +1293,12 @@ class WebsocketHandler {
|
|||||||
numRbfSubs++;
|
numRbfSubs++;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const count = this.wss?.clients?.size || 0;
|
let count = 0;
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
count += server.clients?.size || 0;
|
||||||
|
}
|
||||||
const diff = count - this.numClients;
|
const diff = count - this.numClients;
|
||||||
this.numClients = count;
|
this.numClients = count;
|
||||||
logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`);
|
logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ interface IConfig {
|
|||||||
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
|
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
|
||||||
BACKEND: 'esplora' | 'electrum' | 'none';
|
BACKEND: 'esplora' | 'electrum' | 'none';
|
||||||
HTTP_PORT: number;
|
HTTP_PORT: number;
|
||||||
|
UNIX_SOCKET_PATH: string;
|
||||||
SPAWN_CLUSTER_PROCS: number;
|
SPAWN_CLUSTER_PROCS: number;
|
||||||
API_URL_PREFIX: string;
|
API_URL_PREFIX: string;
|
||||||
POLL_RATE_MS: number;
|
POLL_RATE_MS: number;
|
||||||
@@ -153,6 +154,7 @@ interface IConfig {
|
|||||||
},
|
},
|
||||||
FIAT_PRICE: {
|
FIAT_PRICE: {
|
||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
|
PAID: boolean;
|
||||||
API_KEY: string;
|
API_KEY: string;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -164,6 +166,7 @@ const defaults: IConfig = {
|
|||||||
'NETWORK': 'mainnet',
|
'NETWORK': 'mainnet',
|
||||||
'BACKEND': 'none',
|
'BACKEND': 'none',
|
||||||
'HTTP_PORT': 8999,
|
'HTTP_PORT': 8999,
|
||||||
|
'UNIX_SOCKET_PATH': '',
|
||||||
'SPAWN_CLUSTER_PROCS': 0,
|
'SPAWN_CLUSTER_PROCS': 0,
|
||||||
'API_URL_PREFIX': '/api/v1/',
|
'API_URL_PREFIX': '/api/v1/',
|
||||||
'POLL_RATE_MS': 2000,
|
'POLL_RATE_MS': 2000,
|
||||||
@@ -308,6 +311,7 @@ const defaults: IConfig = {
|
|||||||
},
|
},
|
||||||
'FIAT_PRICE': {
|
'FIAT_PRICE': {
|
||||||
'ENABLED': true,
|
'ENABLED': true,
|
||||||
|
'PAID': false,
|
||||||
'API_KEY': '',
|
'API_KEY': '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,10 +43,14 @@ import redisCache from './api/redis-cache';
|
|||||||
import accelerationApi from './api/services/acceleration';
|
import accelerationApi from './api/services/acceleration';
|
||||||
import bitcoinCoreRoutes from './api/bitcoin/bitcoin-core.routes';
|
import bitcoinCoreRoutes from './api/bitcoin/bitcoin-core.routes';
|
||||||
import bitcoinSecondClient from './api/bitcoin/bitcoin-second-client';
|
import bitcoinSecondClient from './api/bitcoin/bitcoin-second-client';
|
||||||
|
import accelerationRoutes from './api/acceleration/acceleration.routes';
|
||||||
|
import aboutRoutes from './api/about.routes';
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
private wss: WebSocket.Server | undefined;
|
private wss: WebSocket.Server | undefined;
|
||||||
|
private wssUnixSocket: WebSocket.Server | undefined;
|
||||||
private server: http.Server | undefined;
|
private server: http.Server | undefined;
|
||||||
|
private serverUnixSocket: http.Server | undefined;
|
||||||
private app: Application;
|
private app: Application;
|
||||||
private currentBackendRetryInterval = 1;
|
private currentBackendRetryInterval = 1;
|
||||||
private backendRetryCount = 0;
|
private backendRetryCount = 0;
|
||||||
@@ -135,6 +139,10 @@ class Server {
|
|||||||
|
|
||||||
this.server = http.createServer(this.app);
|
this.server = http.createServer(this.app);
|
||||||
this.wss = new WebSocket.Server({ server: this.server });
|
this.wss = new WebSocket.Server({ server: this.server });
|
||||||
|
if (config.MEMPOOL.UNIX_SOCKET_PATH) {
|
||||||
|
this.serverUnixSocket = http.createServer(this.app);
|
||||||
|
this.wssUnixSocket = new WebSocket.Server({ server: this.serverUnixSocket });
|
||||||
|
}
|
||||||
|
|
||||||
this.setUpWebsocketHandling();
|
this.setUpWebsocketHandling();
|
||||||
|
|
||||||
@@ -190,6 +198,16 @@ class Server {
|
|||||||
logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`);
|
logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.serverUnixSocket) {
|
||||||
|
this.serverUnixSocket.listen(config.MEMPOOL.UNIX_SOCKET_PATH, () => {
|
||||||
|
if (worker) {
|
||||||
|
logger.info(`Mempool Server worker #${process.pid} started`);
|
||||||
|
} else {
|
||||||
|
logger.notice(`Mempool Server is listening on ${config.MEMPOOL.UNIX_SOCKET_PATH}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async runMainUpdateLoop(): Promise<void> {
|
async runMainUpdateLoop(): Promise<void> {
|
||||||
@@ -263,8 +281,12 @@ class Server {
|
|||||||
|
|
||||||
setUpWebsocketHandling(): void {
|
setUpWebsocketHandling(): void {
|
||||||
if (this.wss) {
|
if (this.wss) {
|
||||||
websocketHandler.setWebsocketServer(this.wss);
|
websocketHandler.addWebsocketServer(this.wss);
|
||||||
}
|
}
|
||||||
|
if (this.wssUnixSocket) {
|
||||||
|
websocketHandler.addWebsocketServer(this.wssUnixSocket);
|
||||||
|
}
|
||||||
|
|
||||||
if (Common.isLiquid() && config.DATABASE.ENABLED) {
|
if (Common.isLiquid() && config.DATABASE.ENABLED) {
|
||||||
blocks.setNewBlockCallback(async () => {
|
blocks.setNewBlockCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -305,6 +327,10 @@ class Server {
|
|||||||
nodesRoutes.initRoutes(this.app);
|
nodesRoutes.initRoutes(this.app);
|
||||||
channelsRoutes.initRoutes(this.app);
|
channelsRoutes.initRoutes(this.app);
|
||||||
}
|
}
|
||||||
|
if (config.MEMPOOL_SERVICES.ACCELERATIONS) {
|
||||||
|
accelerationRoutes.initRoutes(this.app);
|
||||||
|
}
|
||||||
|
aboutRoutes.initRoutes(this.app);
|
||||||
}
|
}
|
||||||
|
|
||||||
healthCheck(): void {
|
healthCheck(): void {
|
||||||
@@ -332,6 +358,12 @@ class Server {
|
|||||||
if (config.DATABASE.ENABLED) {
|
if (config.DATABASE.ENABLED) {
|
||||||
DB.releasePidLock();
|
DB.releasePidLock();
|
||||||
}
|
}
|
||||||
|
this.server?.close();
|
||||||
|
this.serverUnixSocket?.close();
|
||||||
|
this.wss?.close();
|
||||||
|
if (this.wssUnixSocket) {
|
||||||
|
this.wssUnixSocket.close();
|
||||||
|
}
|
||||||
process.exit(code);
|
process.exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AccelerationInfo, makeBlockTemplate } from '../api/acceleration';
|
import { AccelerationInfo, makeBlockTemplate } from '../api/acceleration/acceleration';
|
||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
import DB from '../database';
|
import DB from '../database';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
@@ -6,8 +6,8 @@ import { IEsploraApi } from '../api/bitcoin/esplora-api.interface';
|
|||||||
import { Common } from '../api/common';
|
import { Common } from '../api/common';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import blocks from '../api/blocks';
|
import blocks from '../api/blocks';
|
||||||
import accelerationApi, { Acceleration } from '../api/services/acceleration';
|
import accelerationApi, { Acceleration, AccelerationHistory } from '../api/services/acceleration';
|
||||||
import accelerationCosts from '../api/acceleration';
|
import accelerationCosts from '../api/acceleration/acceleration';
|
||||||
import bitcoinApi from '../api/bitcoin/bitcoin-api-factory';
|
import bitcoinApi from '../api/bitcoin/bitcoin-api-factory';
|
||||||
import transactionUtils from '../api/transaction-utils';
|
import transactionUtils from '../api/transaction-utils';
|
||||||
import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces';
|
import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces';
|
||||||
@@ -15,6 +15,7 @@ import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces
|
|||||||
export interface PublicAcceleration {
|
export interface PublicAcceleration {
|
||||||
txid: string,
|
txid: string,
|
||||||
height: number,
|
height: number,
|
||||||
|
added: number,
|
||||||
pool: {
|
pool: {
|
||||||
id: number,
|
id: number,
|
||||||
slug: string,
|
slug: string,
|
||||||
@@ -29,15 +30,20 @@ export interface PublicAcceleration {
|
|||||||
class AccelerationRepository {
|
class AccelerationRepository {
|
||||||
private bidBoostV2Activated = 831580;
|
private bidBoostV2Activated = 831580;
|
||||||
|
|
||||||
public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number): Promise<void> {
|
public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number, accelerationData: Acceleration[]): Promise<void> {
|
||||||
|
const accelerationMap: { [txid: string]: Acceleration } = {};
|
||||||
|
for (const acc of accelerationData) {
|
||||||
|
accelerationMap[acc.txid] = acc;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await DB.query(`
|
await DB.query(`
|
||||||
INSERT INTO accelerations(txid, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost)
|
INSERT INTO accelerations(txid, requested, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost)
|
||||||
VALUE (?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)
|
VALUE (?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
height = ?
|
height = ?
|
||||||
`, [
|
`, [
|
||||||
acceleration.txSummary.txid,
|
acceleration.txSummary.txid,
|
||||||
|
accelerationMap[acceleration.txSummary.txid].added,
|
||||||
block.timestamp,
|
block.timestamp,
|
||||||
block.height,
|
block.height,
|
||||||
pool_id,
|
pool_id,
|
||||||
@@ -64,7 +70,7 @@ class AccelerationRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let query = `
|
let query = `
|
||||||
SELECT * FROM accelerations
|
SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations
|
||||||
JOIN pools on pools.unique_id = accelerations.pool
|
JOIN pools on pools.unique_id = accelerations.pool
|
||||||
`;
|
`;
|
||||||
let params: any[] = [];
|
let params: any[] = [];
|
||||||
@@ -99,6 +105,7 @@ class AccelerationRepository {
|
|||||||
return rows.map(row => ({
|
return rows.map(row => ({
|
||||||
txid: row.txid,
|
txid: row.txid,
|
||||||
height: row.height,
|
height: row.height,
|
||||||
|
added: row.requested_timestamp || row.block_timestamp,
|
||||||
pool: {
|
pool: {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
slug: row.slug,
|
slug: row.slug,
|
||||||
@@ -202,7 +209,7 @@ class AccelerationRepository {
|
|||||||
const tx = blockTxs[acc.txid];
|
const tx = blockTxs[acc.txid];
|
||||||
const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions);
|
const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions);
|
||||||
accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost));
|
accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost));
|
||||||
this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id);
|
this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const lastSyncedHeight = await this.$getLastSyncedHeight();
|
const lastSyncedHeight = await this.$getLastSyncedHeight();
|
||||||
@@ -230,7 +237,7 @@ class AccelerationRepository {
|
|||||||
logger.debug(`Fetching accelerations between block ${lastSyncedHeight} and ${currentHeight}`);
|
logger.debug(`Fetching accelerations between block ${lastSyncedHeight} and ${currentHeight}`);
|
||||||
|
|
||||||
// Fetch accelerations from mempool.space since the last synced block;
|
// Fetch accelerations from mempool.space since the last synced block;
|
||||||
const accelerationsByBlock = {};
|
const accelerationsByBlock: {[height: number]: AccelerationHistory[]} = {};
|
||||||
const blockHashes = {};
|
const blockHashes = {};
|
||||||
let done = false;
|
let done = false;
|
||||||
let page = 1;
|
let page = 1;
|
||||||
@@ -297,12 +304,16 @@ class AccelerationRepository {
|
|||||||
const feeStats = Common.calcEffectiveFeeStatistics(template);
|
const feeStats = Common.calcEffectiveFeeStatistics(template);
|
||||||
boostRate = feeStats.medianFee;
|
boostRate = feeStats.medianFee;
|
||||||
}
|
}
|
||||||
|
const accelerationSummaries = accelerations.map(acc => ({
|
||||||
|
...acc,
|
||||||
|
pools: acc.pools.map(pool => pool.pool_unique_id),
|
||||||
|
}))
|
||||||
for (const acc of accelerations) {
|
for (const acc of accelerations) {
|
||||||
if (blockTxs[acc.txid]) {
|
if (blockTxs[acc.txid]) {
|
||||||
const tx = blockTxs[acc.txid];
|
const tx = blockTxs[acc.txid];
|
||||||
const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions);
|
const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions);
|
||||||
accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost));
|
accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost));
|
||||||
await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id);
|
await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, accelerationSummaries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.$setLastSyncedHeight(height);
|
await this.$setLastSyncedHeight(height);
|
||||||
@@ -317,6 +328,26 @@ class AccelerationRepository {
|
|||||||
|
|
||||||
logger.debug(`Indexing accelerations completed`);
|
logger.debug(`Indexing accelerations completed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete accelerations from the database above blockHeight
|
||||||
|
*/
|
||||||
|
public async $deleteAccelerationsFrom(blockHeight: number): Promise<void> {
|
||||||
|
logger.info(`Delete newer accelerations from height ${blockHeight} from the database`);
|
||||||
|
try {
|
||||||
|
const currentSyncedHeight = await this.$getLastSyncedHeight();
|
||||||
|
if (currentSyncedHeight >= blockHeight) {
|
||||||
|
await DB.query(`
|
||||||
|
UPDATE state
|
||||||
|
SET number = ?
|
||||||
|
WHERE name = 'last_acceleration_block'
|
||||||
|
`, [blockHeight - 1]);
|
||||||
|
}
|
||||||
|
await DB.query(`DELETE FROM accelerations where height >= ${blockHeight}`);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err('Cannot delete indexed accelerations. Reason: ' + (e instanceof Error ? e.message : e));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new AccelerationRepository();
|
export default new AccelerationRepository();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import config from '../../config';
|
||||||
import { query } from '../../utils/axios-query';
|
import { query } from '../../utils/axios-query';
|
||||||
import { ConversionFeed, ConversionRates } from '../price-updater';
|
import { ConversionFeed, ConversionRates } from '../price-updater';
|
||||||
|
|
||||||
@@ -37,15 +38,26 @@ const emptyRates = {
|
|||||||
ZAR: -1,
|
ZAR: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FreeCurrencyApi implements ConversionFeed {
|
type PaidCurrencyData = {
|
||||||
private API_KEY: string;
|
[key: string]: {
|
||||||
|
code: string;
|
||||||
constructor(apiKey: string) {
|
value: number;
|
||||||
this.API_KEY = apiKey;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type FreeCurrencyData = {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FreeCurrencyApi implements ConversionFeed {
|
||||||
|
private API_KEY = config.FIAT_PRICE.API_KEY;
|
||||||
|
private PAID = config.FIAT_PRICE.PAID;
|
||||||
|
private API_URL_PREFIX: string = this.PAID ? `https://api.currencyapi.com/v3/` : `https://api.freecurrencyapi.com/v1/`;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
public async $getQuota(): Promise<any> {
|
public async $getQuota(): Promise<any> {
|
||||||
const response = await query(`https://api.freecurrencyapi.com/v1/status?apikey=${this.API_KEY}`);
|
const response = await query(`${this.API_URL_PREFIX}status?apikey=${this.API_KEY}`);
|
||||||
if (response && response['quotas']) {
|
if (response && response['quotas']) {
|
||||||
return response['quotas'];
|
return response['quotas'];
|
||||||
}
|
}
|
||||||
@@ -53,21 +65,36 @@ class FreeCurrencyApi implements ConversionFeed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async $fetchLatestConversionRates(): Promise<ConversionRates> {
|
public async $fetchLatestConversionRates(): Promise<ConversionRates> {
|
||||||
const response = await query(`https://api.freecurrencyapi.com/v1/latest?apikey=${this.API_KEY}`);
|
const response = await query(`${this.API_URL_PREFIX}latest?apikey=${this.API_KEY}`);
|
||||||
if (response && response['data']) {
|
if (response && response['data']) {
|
||||||
|
if (this.PAID) {
|
||||||
|
response['data'] = this.convertData(response['data']);
|
||||||
|
}
|
||||||
return response['data'];
|
return response['data'];
|
||||||
}
|
}
|
||||||
return emptyRates;
|
return emptyRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $fetchConversionRates(date: string): Promise<ConversionRates> {
|
public async $fetchConversionRates(date: string): Promise<ConversionRates> {
|
||||||
const response = await query(`https://api.freecurrencyapi.com/v1/historical?date=${date}&apikey=${this.API_KEY}`);
|
const response = await query(`${this.API_URL_PREFIX}historical?date=${date}&apikey=${this.API_KEY}`);
|
||||||
if (response && response['data'] && response['data'][date]) {
|
if (response && response['data'] && (response['data'][date] || this.PAID)) {
|
||||||
|
if (this.PAID) {
|
||||||
|
response['data'] = this.convertData(response['data']);
|
||||||
|
response['data'][response['meta'].last_updated_at.substr(0, 10)] = response['data'];
|
||||||
|
}
|
||||||
return response['data'][date];
|
return response['data'][date];
|
||||||
}
|
}
|
||||||
return emptyRates;
|
return emptyRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private convertData(data: PaidCurrencyData): FreeCurrencyData {
|
||||||
|
const simplifiedData: FreeCurrencyData = {};
|
||||||
|
for (const key in data) {
|
||||||
|
simplifiedData[key] = data[key].value;
|
||||||
|
}
|
||||||
|
return simplifiedData;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FreeCurrencyApi;
|
export default FreeCurrencyApi;
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class PriceUpdater {
|
|||||||
this.feeds.push(new BitfinexApi());
|
this.feeds.push(new BitfinexApi());
|
||||||
this.feeds.push(new GeminiApi());
|
this.feeds.push(new GeminiApi());
|
||||||
|
|
||||||
this.currencyConversionFeed = new FreeCurrencyApi(config.FIAT_PRICE.API_KEY);
|
this.currencyConversionFeed = new FreeCurrencyApi();
|
||||||
this.setCyclePosition();
|
this.setCyclePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
contributors/daweilv.txt
Normal file
3
contributors/daweilv.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 April 7, 2024.
|
||||||
|
|
||||||
|
Signed: daweilv
|
||||||
3
contributors/henrialb.txt
Normal file
3
contributors/henrialb.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 April 12, 2024.
|
||||||
|
|
||||||
|
Signed: henrialb
|
||||||
@@ -11,10 +11,17 @@ RUN apt-get install -y build-essential python3 pkg-config curl ca-certificates
|
|||||||
|
|
||||||
# Install Rust via rustup
|
# Install Rust via rustup
|
||||||
RUN CPU_ARCH=$(uname -m); if [ "$CPU_ARCH" = "armv7l" ]; then c_rehash; fi
|
RUN CPU_ARCH=$(uname -m); if [ "$CPU_ARCH" = "armv7l" ]; then c_rehash; fi
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
|
#RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
|
||||||
|
#Workaround to run on github actions from https://github.com/rust-lang/rustup/issues/2700#issuecomment-1367488985
|
||||||
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sed 's#/proc/self/exe#\/bin\/sh#g' | sh -s -- -y --default-toolchain stable
|
||||||
ENV PATH="/root/.cargo/bin:$PATH"
|
ENV PATH="/root/.cargo/bin:$PATH"
|
||||||
|
|
||||||
|
COPY --from=backend . .
|
||||||
|
COPY --from=rustgbt . ../rust/
|
||||||
|
ENV FD=/build/rust-gbt
|
||||||
RUN npm install --omit=dev --omit=optional
|
RUN npm install --omit=dev --omit=optional
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
RUN npm run package
|
RUN npm run package
|
||||||
|
|
||||||
FROM node:20.12.0-buster-slim
|
FROM node:20.12.0-buster-slim
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"OFFICIAL": __MEMPOOL_OFFICIAL__,
|
"OFFICIAL": __MEMPOOL_OFFICIAL__,
|
||||||
"HTTP_PORT": __MEMPOOL_HTTP_PORT__,
|
"HTTP_PORT": __MEMPOOL_HTTP_PORT__,
|
||||||
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
|
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
|
||||||
|
"UNIX_SOCKET_PATH": "__MEMPOOL_UNIX_SOCKET_PATH__",
|
||||||
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
||||||
"POLL_RATE_MS": __MEMPOOL_POLL_RATE_MS__,
|
"POLL_RATE_MS": __MEMPOOL_POLL_RATE_MS__,
|
||||||
"CACHE_DIR": "__MEMPOOL_CACHE_DIR__",
|
"CACHE_DIR": "__MEMPOOL_CACHE_DIR__",
|
||||||
@@ -149,6 +150,7 @@
|
|||||||
},
|
},
|
||||||
"FIAT_PRICE": {
|
"FIAT_PRICE": {
|
||||||
"ENABLED": __FIAT_PRICE_ENABLED__,
|
"ENABLED": __FIAT_PRICE_ENABLED__,
|
||||||
|
"PAID": __FIAT_PRICE_PAID__,
|
||||||
"API_KEY": "__FIAT_PRICE_API_KEY__"
|
"API_KEY": "__FIAT_PRICE_API_KEY__"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ __MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=true}
|
|||||||
__MEMPOOL_OFFICIAL__=${MEMPOOL_OFFICIAL:=false}
|
__MEMPOOL_OFFICIAL__=${MEMPOOL_OFFICIAL:=false}
|
||||||
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
|
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
|
||||||
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
|
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
|
||||||
|
__MEMPOOL_UNIX_SOCKET_PATH__=${MEMPOOL_UNIX_SOCKET_PATH:=""}
|
||||||
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
|
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
|
||||||
__MEMPOOL_POLL_RATE_MS__=${MEMPOOL_POLL_RATE_MS:=2000}
|
__MEMPOOL_POLL_RATE_MS__=${MEMPOOL_POLL_RATE_MS:=2000}
|
||||||
__MEMPOOL_CACHE_DIR__=${MEMPOOL_CACHE_DIR:=./cache}
|
__MEMPOOL_CACHE_DIR__=${MEMPOOL_CACHE_DIR:=./cache}
|
||||||
@@ -134,8 +135,8 @@ __MAXMIND_GEOLITE2_ASN__=${MAXMIND_GEOLITE2_ASN:="/backend/GeoIP/GeoLite2-ASN.mm
|
|||||||
__MAXMIND_GEOIP2_ISP__=${MAXMIND_GEOIP2_ISP:=""}
|
__MAXMIND_GEOIP2_ISP__=${MAXMIND_GEOIP2_ISP:=""}
|
||||||
|
|
||||||
# REPLICATION
|
# REPLICATION
|
||||||
__REPLICATION_ENABLED__=${REPLICATION_ENABLED:=true}
|
__REPLICATION_ENABLED__=${REPLICATION_ENABLED:=false}
|
||||||
__REPLICATION_AUDIT__=${REPLICATION_AUDIT:=true}
|
__REPLICATION_AUDIT__=${REPLICATION_AUDIT:=false}
|
||||||
__REPLICATION_AUDIT_START_HEIGHT__=${REPLICATION_AUDIT_START_HEIGHT:=774000}
|
__REPLICATION_AUDIT_START_HEIGHT__=${REPLICATION_AUDIT_START_HEIGHT:=774000}
|
||||||
__REPLICATION_SERVERS__=${REPLICATION_SERVERS:=[]}
|
__REPLICATION_SERVERS__=${REPLICATION_SERVERS:=[]}
|
||||||
|
|
||||||
@@ -150,6 +151,7 @@ __REDIS_BATCH_QUERY_BASE_SIZE__=${REDIS_BATCH_QUERY_BASE_SIZE:=5000}
|
|||||||
|
|
||||||
# FIAT_PRICE
|
# FIAT_PRICE
|
||||||
__FIAT_PRICE_ENABLED__=${FIAT_PRICE_ENABLED:=true}
|
__FIAT_PRICE_ENABLED__=${FIAT_PRICE_ENABLED:=true}
|
||||||
|
__FIAT_PRICE_PAID__=${FIAT_PRICE_PAID:=false}
|
||||||
__FIAT_PRICE_API_KEY__=${FIAT_PRICE_API_KEY:=""}
|
__FIAT_PRICE_API_KEY__=${FIAT_PRICE_API_KEY:=""}
|
||||||
|
|
||||||
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
||||||
@@ -160,6 +162,7 @@ sed -i "s!__MEMPOOL_ENABLED__!${__MEMPOOL_ENABLED__}!g" mempool-config.json
|
|||||||
sed -i "s!__MEMPOOL_OFFICIAL__!${__MEMPOOL_OFFICIAL__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_OFFICIAL__!${__MEMPOOL_OFFICIAL__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_HTTP_PORT__!${__MEMPOOL_HTTP_PORT__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_HTTP_PORT__!${__MEMPOOL_HTTP_PORT__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_SPAWN_CLUSTER_PROCS__!${__MEMPOOL_SPAWN_CLUSTER_PROCS__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_SPAWN_CLUSTER_PROCS__!${__MEMPOOL_SPAWN_CLUSTER_PROCS__}!g" mempool-config.json
|
||||||
|
sed -i "s!__MEMPOOL_UNIX_SOCKET_PATH__!${__MEMPOOL_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_POLL_RATE_MS__!${__MEMPOOL_POLL_RATE_MS__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_POLL_RATE_MS__!${__MEMPOOL_POLL_RATE_MS__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_CACHE_DIR__!${__MEMPOOL_CACHE_DIR__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_CACHE_DIR__!${__MEMPOOL_CACHE_DIR__}!g" mempool-config.json
|
||||||
@@ -294,6 +297,7 @@ sed -i "s!__REDIS_BATCH_QUERY_BASE_SIZE__!${__REDIS_BATCH_QUERY_BASE_SIZE__}!g"
|
|||||||
|
|
||||||
# FIAT_PRICE
|
# FIAT_PRICE
|
||||||
sed -i "s!__FIAT_PRICE_ENABLED__!${__FIAT_PRICE_ENABLED__}!g" mempool-config.json
|
sed -i "s!__FIAT_PRICE_ENABLED__!${__FIAT_PRICE_ENABLED__}!g" mempool-config.json
|
||||||
|
sed -i "s!__FIAT_PRICE_PAID__!${__FIAT_PRICE_PAID__}!g" mempool-config.json
|
||||||
sed -i "s!__FIAT_PRICE_API_KEY__!${__FIAT_PRICE_API_KEY__}!g" mempool-config.json
|
sed -i "s!__FIAT_PRICE_API_KEY__!${__FIAT_PRICE_API_KEY__}!g" mempool-config.json
|
||||||
|
|
||||||
node /backend/package/index.js
|
node /backend/package/index.js
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ __MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
|||||||
__TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_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}
|
__SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||||
__ACCELERATOR__=${ACCELERATOR:=false}
|
__ACCELERATOR__=${ACCELERATOR:=false}
|
||||||
|
__PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false}
|
||||||
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
|
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
|
||||||
__ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
|
__ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
|
|||||||
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
|
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
|
||||||
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
|
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
|
||||||
export __ACCELERATOR__
|
export __ACCELERATOR__
|
||||||
|
export __PUBLIC_ACCELERATIONS__
|
||||||
export __HISTORICAL_PRICE__
|
export __HISTORICAL_PRICE__
|
||||||
export __ADDITIONAL_CURRENCIES__
|
export __ADDITIONAL_CURRENCIES__
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,16 @@
|
|||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
|
{
|
||||||
|
"input": "src/theme-contrast.scss",
|
||||||
|
"bundleName": "contrast",
|
||||||
|
"inject": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "src/theme-wiz.scss",
|
||||||
|
"bundleName": "wiz",
|
||||||
|
"inject": false
|
||||||
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
||||||
],
|
],
|
||||||
"vendorChunk": true,
|
"vendorChunk": true,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe('Mainnet', () => {
|
|||||||
cy.get('[id^="bitcoin-block-"]').should('have.length', 22);
|
cy.get('[id^="bitcoin-block-"]').should('have.length', 22);
|
||||||
cy.get('.footer').should('be.visible');
|
cy.get('.footer').should('be.visible');
|
||||||
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
|
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
|
||||||
expect(text).to.match(/Incoming transactions.* vB\/s/);
|
expect(text).to.match(/Incoming Transactions.* vB\/s/);
|
||||||
});
|
});
|
||||||
cy.get('.row > :nth-child(2)').invoke('text').then((text) => {
|
cy.get('.row > :nth-child(2)').invoke('text').then((text) => {
|
||||||
expect(text).to.match(/Unconfirmed:(.*)/);
|
expect(text).to.match(/Unconfirmed:(.*)/);
|
||||||
|
|||||||
@@ -21,5 +21,6 @@
|
|||||||
"LIGHTNING": false,
|
"LIGHTNING": false,
|
||||||
"HISTORICAL_PRICE": true,
|
"HISTORICAL_PRICE": true,
|
||||||
"ADDITIONAL_CURRENCIES": false,
|
"ADDITIONAL_CURRENCIES": false,
|
||||||
"ACCELERATOR": false
|
"ACCELERATOR": false,
|
||||||
|
"PUBLIC_ACCELERATIONS": false
|
||||||
}
|
}
|
||||||
|
|||||||
15
frontend/package-lock.json
generated
15
frontend/package-lock.json
generated
@@ -32,6 +32,7 @@
|
|||||||
"bootstrap": "~4.6.2",
|
"bootstrap": "~4.6.2",
|
||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
|
"cypress": "^13.8.0",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.5.0",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.20.2",
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.5.0",
|
"@cypress/schematic": "^2.5.0",
|
||||||
"@types/cypress": "^1.1.3",
|
"@types/cypress": "^1.1.3",
|
||||||
"cypress": "^13.7.0",
|
"cypress": "^13.8.0",
|
||||||
"cypress-fail-on-console-error": "~5.1.0",
|
"cypress-fail-on-console-error": "~5.1.0",
|
||||||
"cypress-wait-until": "^2.0.1",
|
"cypress-wait-until": "^2.0.1",
|
||||||
"mock-socket": "~9.3.1",
|
"mock-socket": "~9.3.1",
|
||||||
@@ -8028,9 +8029,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "13.7.0",
|
"version": "13.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.0.tgz",
|
||||||
"integrity": "sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==",
|
"integrity": "sha512-Qau//mtrwEGOU9cn2YjavECKyDUwBh8J2tit+y9s1wsv6C3BX+rlv6I9afmQnL8PmEEzJ6be7nppMHacFzZkTw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -24111,9 +24112,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"cypress": {
|
"cypress": {
|
||||||
"version": "13.7.0",
|
"version": "13.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.0.tgz",
|
||||||
"integrity": "sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==",
|
"integrity": "sha512-Qau//mtrwEGOU9cn2YjavECKyDUwBh8J2tit+y9s1wsv6C3BX+rlv6I9afmQnL8PmEEzJ6be7nppMHacFzZkTw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cypress/request": "^3.0.0",
|
"@cypress/request": "^3.0.0",
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.5.0",
|
"@cypress/schematic": "^2.5.0",
|
||||||
"@types/cypress": "^1.1.3",
|
"@types/cypress": "^1.1.3",
|
||||||
"cypress": "^13.7.0",
|
"cypress": "^13.8.0",
|
||||||
"cypress-fail-on-console-error": "~5.1.0",
|
"cypress-fail-on-console-error": "~5.1.0",
|
||||||
"cypress-wait-until": "^2.0.1",
|
"cypress-wait-until": "^2.0.1",
|
||||||
"mock-socket": "~9.3.1",
|
"mock-socket": "~9.3.1",
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { MempoolBlockViewComponent } from './components/mempool-block-view/mempo
|
|||||||
import { ClockComponent } from './components/clock/clock.component';
|
import { ClockComponent } from './components/clock/clock.component';
|
||||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||||
import { AddressGroupComponent } from './components/address-group/address-group.component';
|
import { AddressGroupComponent } from './components/address-group/address-group.component';
|
||||||
|
import { TrackerComponent } from './components/tracker/tracker.component';
|
||||||
|
import { AccelerateCheckout } from './components/accelerate-checkout/accelerate-checkout.component';
|
||||||
|
|
||||||
const browserWindow = window || {};
|
const browserWindow = window || {};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -105,6 +107,10 @@ let routes: Routes = [
|
|||||||
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
|
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
|
||||||
data: { preload: true },
|
data: { preload: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'tracker/:id',
|
||||||
|
component: TrackerComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'wallet',
|
path: 'wallet',
|
||||||
children: [],
|
children: [],
|
||||||
@@ -263,7 +269,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||||||
imports: [RouterModule.forRoot(routes, {
|
imports: [RouterModule.forRoot(routes, {
|
||||||
initialNavigation: 'enabledBlocking',
|
initialNavigation: 'enabledBlocking',
|
||||||
scrollPositionRestoration: 'enabled',
|
scrollPositionRestoration: 'enabled',
|
||||||
anchorScrolling: 'enabled',
|
anchorScrolling: 'disabled',
|
||||||
preloadingStrategy: AppPreloadingStrategy
|
preloadingStrategy: AppPreloadingStrategy
|
||||||
})],
|
})],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const mempoolFeeColors = [
|
export const defaultMempoolFeeColors = [
|
||||||
'557d00',
|
'557d00',
|
||||||
'5d7d01',
|
'5d7d01',
|
||||||
'637d02',
|
'637d02',
|
||||||
@@ -39,6 +39,47 @@ export const mempoolFeeColors = [
|
|||||||
'ae005b',
|
'ae005b',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const contrastMempoolFeeColors = [
|
||||||
|
'0082e6',
|
||||||
|
'0984df',
|
||||||
|
'1285d9',
|
||||||
|
'1a87d2',
|
||||||
|
'2388cb',
|
||||||
|
'2c8ac5',
|
||||||
|
'358bbe',
|
||||||
|
'3e8db7',
|
||||||
|
'468eb0',
|
||||||
|
'4f90aa',
|
||||||
|
'5892a3',
|
||||||
|
'61939c',
|
||||||
|
'6a9596',
|
||||||
|
'72968f',
|
||||||
|
'7b9888',
|
||||||
|
'849982',
|
||||||
|
'8d9b7b',
|
||||||
|
'959c74',
|
||||||
|
'9e9e6e',
|
||||||
|
'a79f67',
|
||||||
|
'b0a160',
|
||||||
|
'b9a35a',
|
||||||
|
'c1a453',
|
||||||
|
'caa64c',
|
||||||
|
'd3a745',
|
||||||
|
'dca93f',
|
||||||
|
'e5aa38',
|
||||||
|
'edac31',
|
||||||
|
'f6ad2b',
|
||||||
|
'ffaf24',
|
||||||
|
'ffb01e',
|
||||||
|
'ffb118',
|
||||||
|
'ffb212',
|
||||||
|
'ffb30c',
|
||||||
|
'ffb406',
|
||||||
|
'ffb500',
|
||||||
|
'ffb600',
|
||||||
|
'ffb700',
|
||||||
|
];
|
||||||
|
|
||||||
export const chartColors = [
|
export const chartColors = [
|
||||||
"#D81B60",
|
"#D81B60",
|
||||||
"#8E24AA",
|
"#8E24AA",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { SharedModule } from './shared/shared.module';
|
|||||||
import { StorageService } from './services/storage.service';
|
import { StorageService } from './services/storage.service';
|
||||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||||
import { LanguageService } from './services/language.service';
|
import { LanguageService } from './services/language.service';
|
||||||
|
import { ThemeService } from './services/theme.service';
|
||||||
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
|
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
|
||||||
import { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe';
|
import { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe';
|
||||||
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||||
@@ -38,6 +39,7 @@ const providers = [
|
|||||||
StorageService,
|
StorageService,
|
||||||
EnterpriseService,
|
EnterpriseService,
|
||||||
LanguageService,
|
LanguageService,
|
||||||
|
ThemeService,
|
||||||
ShortenStringPipe,
|
ShortenStringPipe,
|
||||||
FiatShortenerPipe,
|
FiatShortenerPipe,
|
||||||
FiatCurrencyPipe,
|
FiatCurrencyPipe,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.become-sponsor {
|
.become-sponsor {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<track label="Português" kind="captions" srclang="pt" src="/resources/promo-video/pt.vtt" [attr.default]="showSubtitles('pt') ? '' : null">
|
<track label="Português" kind="captions" srclang="pt" src="/resources/promo-video/pt.vtt" [attr.default]="showSubtitles('pt') ? '' : null">
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<ng-container *ngIf="officialMempoolSpace">
|
<ng-container>
|
||||||
<app-about-sponsors></app-about-sponsors>
|
<app-about-sponsors></app-about-sponsors>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="officialMempoolSpace">
|
<ng-container>
|
||||||
<div *ngIf="profiles$ | async as profiles" id="community-sponsors-anchor">
|
<div *ngIf="profiles$ | async as profiles" id="community-sponsors-anchor">
|
||||||
<div class="community-sponsor" style="margin-bottom: 68px" *ngIf="profiles.whales.length > 0">
|
<div class="community-sponsor" style="margin-bottom: 68px" *ngIf="profiles.whales.length > 0">
|
||||||
<h3 i18n="about.sponsors.withHeart">Whale Sponsors</h3>
|
<h3 i18n="about.sponsors.withHeart">Whale Sponsors</h3>
|
||||||
@@ -380,7 +380,7 @@
|
|||||||
<h3 i18n="about.project_members">Project Members</h3>
|
<h3 i18n="about.project_members">Project Members</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
|
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
|
||||||
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
|
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name" [class]="'project-member-avatar'">
|
||||||
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" onError="this.src = '/resources/profile/unknown.svg'; this.className = 'image unknown'"/>
|
||||||
<span>{{ contributor.name }}</span>
|
<span>{{ contributor.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -177,6 +177,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#project-members a.project-member-avatar img {
|
||||||
|
margin: 40px 20px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.copyright {
|
.copyright {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
max-width: 620px;
|
max-width: 620px;
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<div class="container-md card w-100" style="padding: 1em; background: var(--box-bg)" id=acceleratePreviewAnchor>
|
||||||
|
|
||||||
|
@if (error) {
|
||||||
|
<div class="mt-2">
|
||||||
|
<app-mempool-error [error]="error"></app-mempool-error>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if (step === 'cta') {
|
||||||
|
<!-- Show A/B CTAs -->
|
||||||
|
<div class="row mb-1">
|
||||||
|
<div class="col-sm">
|
||||||
|
<h1 style="font-size: larger;">Accelerate your Bitcoin transaction?</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group form-check mb-2">
|
||||||
|
<input type="radio" class="form-check-input" id="accelerate" name="accelerate" (change)="selectedOptionChanged($event)">
|
||||||
|
<label class="form-check-label d-flex flex-column" for="accelerate">
|
||||||
|
<span class="font-weight-bold">Accelerate</span>
|
||||||
|
<span style="color: rgb(186, 186, 186); font-size: 14px;">Confirmation expected within ~30 minutes<br>
|
||||||
|
@if (!calculating) {
|
||||||
|
<app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small> <span class="symbol" i18n="shared.sats|sats">sats</span></span>)
|
||||||
|
} @else {
|
||||||
|
<span class="estimating">Calculating cost...</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group form-check mb-2">
|
||||||
|
<input type="radio" class="form-check-input" id="wait" name="accelerate" (change)="selectedOptionChanged($event)">
|
||||||
|
<label class="form-check-label d-flex flex-column" for="wait">
|
||||||
|
<span class="font-weight-bold">Wait</span>
|
||||||
|
@if (eta) {
|
||||||
|
<span style="color: rgb(186, 186, 186); font-size: 14px;">Confirmation expected <app-time kind="within" [time]="eta" [fastRender]="false" [fixedRender]="true"></app-time></span>
|
||||||
|
} @else {
|
||||||
|
<span style="color: rgb(186, 186, 186); font-size: 14px;">
|
||||||
|
<span>Settlement expected within several hours</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2 mb-2" [style]="(choosenOption === 'wait' || calculating) ? 'opacity: 0.25; pointer-events: none' : ''">
|
||||||
|
<div class="col-sm d-flex flex-row justify-content-center">
|
||||||
|
<button type="button" class="mt-1 btn btn-purple rounded-pill align-self-center d-flex flex-row justify-content-center align-items-center" style="width: 200px" (click)="enableCheckoutPage()">
|
||||||
|
<img src="/resources/mempool-accelerator-sparkles-light.svg" height="20" class="mr-2" style="margin-left: -10px">
|
||||||
|
<span>Accelerate</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if (step === 'checkout') {
|
||||||
|
<!-- Show checkout page -->
|
||||||
|
<div class="row mb-md-1 text-center">
|
||||||
|
<div class="col-sm">
|
||||||
|
<h1 style="font-size: larger;">Confirm your payment</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group w-100" style="font-size: 14px">
|
||||||
|
Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + txid" target="_blank">{{ txid.substr(0, 10) }}..{{ txid.substr(-10) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!loadingCashapp) {
|
||||||
|
<div class="row text-center mt-1">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group w-100">
|
||||||
|
<span><u><strong>Total additional cost</strong></u><br>
|
||||||
|
<span style="font-size: 16px" class="d-block mt-2">
|
||||||
|
Pay
|
||||||
|
<strong><app-fiat [value]="cost"></app-fiat></strong>
|
||||||
|
with
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row text-center mt-1">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group w-100">
|
||||||
|
<div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
||||||
|
@if (loadingCashapp) {
|
||||||
|
<div display="d-flex flex-row justify-content-center">
|
||||||
|
<span>Loading payment method...</span>
|
||||||
|
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div class="row mt-2 mb-2 text-center">
|
||||||
|
<div class="col-sm d-flex flex-column">
|
||||||
|
<small>Changed your mind?</small>
|
||||||
|
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="step = 'cta'">Go Back</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if (step === 'processing') {
|
||||||
|
<div class="row mb-1 text-center">
|
||||||
|
<div class="col-sm">
|
||||||
|
<h1 style="font-size: larger;">Confirm your payment</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row text-center mt-1">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group w-100">
|
||||||
|
<!-- Processing payment -->
|
||||||
|
<div id="cash-app-pay" class="d-inline-block" [style]="'opacity: 0; width: 0px; height: 0px; pointer-events: none;'"></div>
|
||||||
|
<div display="d-flex flex-row justify-content-center">
|
||||||
|
<span>We are processing your payment...</span>
|
||||||
|
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estimating {
|
||||||
|
color: var(--green)
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges } from '@angular/core';
|
||||||
|
import { Subscription, tap, of, catchError } from 'rxjs';
|
||||||
|
import { ServicesApiServices } from '../../services/services-api.service';
|
||||||
|
import { nextRoundNumber } from '../../shared/common.utils';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { AudioService } from '../../services/audio.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-accelerate-checkout',
|
||||||
|
templateUrl: './accelerate-checkout.component.html',
|
||||||
|
styleUrls: ['./accelerate-checkout.component.scss']
|
||||||
|
})
|
||||||
|
export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||||
|
@Input() eta: number | null = null;
|
||||||
|
@Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad';
|
||||||
|
@Input() scrollEvent: boolean;
|
||||||
|
@Output() close = new EventEmitter<null>();
|
||||||
|
|
||||||
|
calculating = true;
|
||||||
|
choosenOption: 'wait' | 'accelerate' = 'wait';
|
||||||
|
error = '';
|
||||||
|
|
||||||
|
// accelerator stuff
|
||||||
|
square: { appId: string, locationId: string};
|
||||||
|
accelerationUUID: string;
|
||||||
|
estimateSubscription: Subscription;
|
||||||
|
maxBidBoost: number; // sats
|
||||||
|
cost: number; // sats
|
||||||
|
|
||||||
|
// square
|
||||||
|
loadingCashapp = false;
|
||||||
|
cashappSubmit: any;
|
||||||
|
payments: any;
|
||||||
|
cashAppPay: any;
|
||||||
|
cashAppSubscription: Subscription;
|
||||||
|
conversionsSubscription: Subscription;
|
||||||
|
step: 'cta' | 'checkout' | 'processing' = 'cta';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private servicesApiService: ServicesApiServices,
|
||||||
|
private stateService: StateService,
|
||||||
|
private audioService: AudioService,
|
||||||
|
private cd: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
this.accelerationUUID = window.crypto.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (urlParams.get('cash_request_id')) { // Redirected from cashapp
|
||||||
|
this.insertSquare();
|
||||||
|
this.setupSquare();
|
||||||
|
this.step = 'processing';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.servicesApiService.setupSquare$().subscribe(ids => {
|
||||||
|
this.square = {
|
||||||
|
appId: ids.squareAppId,
|
||||||
|
locationId: ids.squareLocationId
|
||||||
|
};
|
||||||
|
if (this.step === 'cta') {
|
||||||
|
this.estimate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.estimateSubscription) {
|
||||||
|
this.estimateSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes.scrollEvent) {
|
||||||
|
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll to element id with or without setTimeout
|
||||||
|
*/
|
||||||
|
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.scrollToPreview(id, position);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
scrollToPreview(id: string, position: ScrollLogicalPosition) {
|
||||||
|
const acceleratePreviewAnchor = document.getElementById(id);
|
||||||
|
if (acceleratePreviewAnchor) {
|
||||||
|
this.cd.markForCheck();
|
||||||
|
acceleratePreviewAnchor.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
inline: position,
|
||||||
|
block: position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accelerator
|
||||||
|
*/
|
||||||
|
estimate() {
|
||||||
|
if (this.estimateSubscription) {
|
||||||
|
this.estimateSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
this.calculating = true;
|
||||||
|
this.estimateSubscription = this.servicesApiService.estimate$(this.txid).pipe(
|
||||||
|
tap((response) => {
|
||||||
|
this.calculating = false;
|
||||||
|
if (response.status === 204) {
|
||||||
|
this.error = `cannot_accelerate_tx`;
|
||||||
|
} else {
|
||||||
|
const estimation = response.body;
|
||||||
|
if (!estimation) {
|
||||||
|
this.error = `cannot_accelerate_tx`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Make min extra fee at least 50% of the current tx fee
|
||||||
|
const minExtraBoost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee));
|
||||||
|
const DEFAULT_BID_RATIO = 2;
|
||||||
|
this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO;
|
||||||
|
this.cost = this.maxBidBoost + estimation.mempoolBaseFee + estimation.vsizeFee;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
catchError((response) => {
|
||||||
|
this.error = `cannot_accelerate_tx`;
|
||||||
|
return of(null);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Square
|
||||||
|
*/
|
||||||
|
insertSquare(): void {
|
||||||
|
//@ts-ignore
|
||||||
|
if (window.Square) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let statsUrl = 'https://sandbox.web.squarecdn.com/v1/square.js';
|
||||||
|
if (document.location.hostname === 'mempool-staging.fmt.mempool.space' ||
|
||||||
|
document.location.hostname === 'mempool-staging.va1.mempool.space' ||
|
||||||
|
document.location.hostname === 'mempool-staging.fra.mempool.space' ||
|
||||||
|
document.location.hostname === 'mempool-staging.tk7.mempool.space' ||
|
||||||
|
document.location.hostname === 'mempool.space') {
|
||||||
|
statsUrl = 'https://web.squarecdn.com/v1/square.js';
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||||
|
// @ts-ignore
|
||||||
|
g.type='text/javascript'; g.src=statsUrl; s.parentNode.insertBefore(g, s);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
setupSquare() {
|
||||||
|
const init = () => {
|
||||||
|
this.initSquare();
|
||||||
|
};
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
if (!window.Square) {
|
||||||
|
console.debug('Square.js failed to load properly. Retrying in 1 second.');
|
||||||
|
setTimeout(init, 1000);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async initSquare(): Promise<void> {
|
||||||
|
try {
|
||||||
|
//@ts-ignore
|
||||||
|
this.payments = window.Square.payments(this.square.appId, this.square.locationId)
|
||||||
|
await this.requestCashAppPayment();
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('Error loading Square Payments', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async requestCashAppPayment() {
|
||||||
|
if (this.cashAppSubscription) {
|
||||||
|
this.cashAppSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.conversionsSubscription) {
|
||||||
|
this.conversionsSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||||
|
async (conversions) => {
|
||||||
|
if (this.cashAppPay) {
|
||||||
|
this.cashAppPay.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`;
|
||||||
|
const costUSD = this.step === 'processing' ? 69.69 : (this.cost / 100_000_000 * conversions.USD); // When we're redirected to this component, the payment data is already linked to the payment token, so does not matter what amonut we put in there, therefore it's 69.69
|
||||||
|
const paymentRequest = this.payments.paymentRequest({
|
||||||
|
countryCode: 'US',
|
||||||
|
currencyCode: 'USD',
|
||||||
|
total: {
|
||||||
|
amount: costUSD.toString(),
|
||||||
|
label: 'Total',
|
||||||
|
pending: true,
|
||||||
|
productUrl: `${redirectHostname}/tracker/${this.txid}`,
|
||||||
|
},
|
||||||
|
button: { shape: 'semiround', size: 'small', theme: 'light'}
|
||||||
|
});
|
||||||
|
this.cashAppPay = await this.payments.cashAppPay(paymentRequest, {
|
||||||
|
redirectURL: `${redirectHostname}/tracker/${this.txid}`,
|
||||||
|
referenceId: `accelerator-${this.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
|
button: { shape: 'semiround', size: 'small', theme: 'light'}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.step === 'checkout') {
|
||||||
|
await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'light', size: 'small', shape: 'semiround' })
|
||||||
|
}
|
||||||
|
this.loadingCashapp = false;
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.cashAppPay.addEventListener('ontokenization', function (event) {
|
||||||
|
const { tokenResult, error } = event.detail;
|
||||||
|
if (error) {
|
||||||
|
this.error = error;
|
||||||
|
} else if (tokenResult.status === 'OK') {
|
||||||
|
that.servicesApiService.accelerateWithCashApp$(
|
||||||
|
that.txid,
|
||||||
|
tokenResult.token,
|
||||||
|
tokenResult.details.cashAppPay.cashtag,
|
||||||
|
tokenResult.details.cashAppPay.referenceId,
|
||||||
|
that.accelerationUUID
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
that.audioService.playSound('ascend-chime-cartoon');
|
||||||
|
if (that.cashAppPay) {
|
||||||
|
that.cashAppPay.destroy();
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
that.closeModal();
|
||||||
|
if (window.history.replaceState) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
window.history.replaceState(null, null, window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ''));
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
error: (response) => {
|
||||||
|
if (response.status === 403 && response.error === 'not_available') {
|
||||||
|
that.error = 'waitlisted';
|
||||||
|
} else {
|
||||||
|
that.error = response.error;
|
||||||
|
setTimeout(() => {
|
||||||
|
// Reset everything by reloading the page :D, can be improved
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI events
|
||||||
|
*/
|
||||||
|
enableCheckoutPage() {
|
||||||
|
this.step = 'checkout';
|
||||||
|
this.loadingCashapp = true;
|
||||||
|
this.insertSquare();
|
||||||
|
this.setupSquare();
|
||||||
|
}
|
||||||
|
selectedOptionChanged(event) {
|
||||||
|
this.choosenOption = event.target.id;
|
||||||
|
}
|
||||||
|
closeModal(): void {
|
||||||
|
this.close.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
|
|
||||||
.bar {
|
.bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
&.tx {
|
&.tx {
|
||||||
.fill {
|
.fill {
|
||||||
background: #3bcc49;
|
background: var(--green);
|
||||||
}
|
}
|
||||||
.line {
|
.line {
|
||||||
.fee-rate {
|
.fee-rate {
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
|
|
||||||
&.target {
|
&.target {
|
||||||
.fill {
|
.fill {
|
||||||
background: #653b9c;
|
background: var(--tertiary);
|
||||||
}
|
}
|
||||||
.fee {
|
.fee {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
}
|
}
|
||||||
&.active, &:hover {
|
&.active, &:hover {
|
||||||
.fill {
|
.fill {
|
||||||
background: #105fb0;
|
background: var(--primary);
|
||||||
}
|
}
|
||||||
.line {
|
.line {
|
||||||
.fee-rate .label {
|
.fee-rate .label {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
|
|||||||
rate: option.rate,
|
rate: option.rate,
|
||||||
style: this.getStyle(option.rate, maxRate, baseHeight),
|
style: this.getStyle(option.rate, maxRate, baseHeight),
|
||||||
class: 'max',
|
class: 'max',
|
||||||
label: 'maximum',
|
label: $localize`maximum`,
|
||||||
active: option.index === this.maxRateIndex,
|
active: option.index === this.maxRateIndex,
|
||||||
rateIndex: option.index,
|
rateIndex: option.index,
|
||||||
fee: option.fee,
|
fee: option.fee,
|
||||||
@@ -63,7 +63,7 @@ export class AccelerateFeeGraphComponent implements OnInit, OnChanges {
|
|||||||
rate: this.estimate.targetFeeRate,
|
rate: this.estimate.targetFeeRate,
|
||||||
style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
|
style: this.getStyle(this.estimate.targetFeeRate, maxRate, baseHeight),
|
||||||
class: 'target',
|
class: 'target',
|
||||||
label: 'next block',
|
label: $localize`:@@bdf0e930eb22431140a2eaeacd809cc5f8ebd38c:Next Block`.toLowerCase(),
|
||||||
fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
|
fee: this.estimate.nextBlockFee - this.estimate.txSummary.effectiveFee
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,42 +26,38 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="estimate else loadingEstimate">
|
<ng-container *ngIf="estimate else loadingEstimate">
|
||||||
<div [class]="{estimateDisabled: error}">
|
<div [class]="{estimateDisabled: error || showSuccess }">
|
||||||
|
|
||||||
<div *ngIf="user && !estimate.hasAccess">
|
<div *ngIf="user && !estimate.hasAccess">
|
||||||
<div class="alert alert-mempool">You are currently on the waitlist</div>
|
<div class="alert alert-mempool">You are currently on the waitlist</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5>Your transaction</h5>
|
<h5 i18n="accelerator.your-transaction">Your transaction</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
|
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
|
||||||
Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor{{ estimate.txSummary.ancestorCount > 2 ? 's' : ''}}.
|
<ng-container i18n="accelerator.plus-unconfirmed-ancestors">Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor(s)</ng-container>
|
||||||
</small>
|
</small>
|
||||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
<table class="table table-borderless table-border table-dark table-background table-accelerator">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="group-first">
|
<tr class="group-first">
|
||||||
<td class="item">
|
<td class="item" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||||
Virtual size
|
|
||||||
</td>
|
|
||||||
<td style="text-align: end;" [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
|
<td style="text-align: end;" [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="info">
|
<tr class="info">
|
||||||
<td class="info" colspan=3>
|
<td class="info" colspan=3>
|
||||||
<i><small>Size in vbytes of this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
|
<i><small i18n="accelerator.transaction-vbytes-size-description">Size in vbytes of this transaction (including unconfirmed ancestors)</small></i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="item">
|
<td class="item" i18n="accelerator.in-band-fees">In-band fees</td>
|
||||||
In-band fees
|
|
||||||
</td>
|
|
||||||
<td style="text-align: end;">
|
<td style="text-align: end;">
|
||||||
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
|
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="info group-last">
|
<tr class="info group-last">
|
||||||
<td class="info" colspan=3>
|
<td class="info" colspan=3>
|
||||||
<i><small>Fees already paid by this transaction<span *ngIf="hasAncestors"> and its unconfirmed ancestors</span></small></i>
|
<i><small i18n="accelerator.fees-already-paid-description">Fees already paid by this transaction (including unconfirmed ancestors)</small></i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -69,13 +65,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<h5>How much more are you willing to pay?</h5>
|
<h5 i18n="accelerator.pay-how-much">How much more are you willing to pay?</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<small class="form-text text-muted mb-2">
|
<small class="form-text text-muted mb-2" i18n="accelerator.transaction-fee-description">Choose the maximum extra transaction fee you're willing to pay to get into the next block.</small>
|
||||||
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="form-group">
|
||||||
<div class="fee-card">
|
<div class="fee-card">
|
||||||
<div class="d-flex mb-0">
|
<div class="d-flex mb-0">
|
||||||
@@ -94,14 +87,12 @@
|
|||||||
<h5>Acceleration summary</h5>
|
<h5>Acceleration summary</h5>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<table class="table table-borderless table-border table-dark table-accelerator">
|
<table class="table table-borderless table-border table-dark table-background table-accelerator">
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- ESTIMATED FEE -->
|
<!-- ESTIMATED FEE -->
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<tr class="group-first">
|
<tr class="group-first">
|
||||||
<td class="item">
|
<td class="item" i18n="accelerator.next-block-rate">Next block market rate</td>
|
||||||
Next block market rate
|
|
||||||
</td>
|
|
||||||
<td class="amt" style="font-size: 16px">
|
<td class="amt" style="font-size: 16px">
|
||||||
{{ estimate.targetFeeRate | number : '1.0-0' }}
|
{{ estimate.targetFeeRate | number : '1.0-0' }}
|
||||||
</td>
|
</td>
|
||||||
@@ -109,7 +100,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="info">
|
<tr class="info">
|
||||||
<td class="info">
|
<td class="info">
|
||||||
<i><small>Estimated extra fee required</small></i>
|
<i><small i18n="accelerator.estimated-extra-fee-required">Estimated extra fee required</small></i>
|
||||||
</td>
|
</td>
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
|
{{ math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee) | number }}
|
||||||
@@ -123,13 +114,11 @@
|
|||||||
|
|
||||||
<!-- MEMPOOL BASE FEE -->
|
<!-- MEMPOOL BASE FEE -->
|
||||||
<tr>
|
<tr>
|
||||||
<td class="item">
|
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator™ fees</td>
|
||||||
Mempool Accelerator™ fees
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="info">
|
<tr class="info">
|
||||||
<td class="info">
|
<td class="info">
|
||||||
<i><small>Accelerator Service Fee</small></i>
|
<i><small i18n="accelerator.service-fee">Accelerator Service Fee</small></i>
|
||||||
</td>
|
</td>
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
+{{ estimate.mempoolBaseFee | number }}
|
+{{ estimate.mempoolBaseFee | number }}
|
||||||
@@ -141,7 +130,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="info group-last">
|
<tr class="info group-last">
|
||||||
<td class="info">
|
<td class="info">
|
||||||
<i><small>Transaction Size Surcharge</small></i>
|
<i><small i18n="accelerator.tx-size-surcharge">Transaction Size Surcharge</small></i>
|
||||||
</td>
|
</td>
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
+{{ estimate.vsizeFee | number }}
|
+{{ estimate.vsizeFee | number }}
|
||||||
@@ -152,11 +141,12 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
<!-- NEXT BLOCK ESTIMATE -->
|
<!-- NEXT BLOCK ESTIMATE -->
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<tr class="group-first" style="border-top: 1px dashed grey; border-collapse: collapse;">
|
<tr class="group-first" style="border-top: 1px dashed grey; border-collapse: collapse;">
|
||||||
<td class="item">
|
<td class="item">
|
||||||
<b style="background-color: #5E35B1" class="p-1 pl-0">Estimated acceleration cost</b>
|
<b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
||||||
@@ -170,19 +160,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
|
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
|
||||||
<td class="info" colspan=3>
|
<td class="info" colspan=3>
|
||||||
<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>
|
<i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: estimate.targetFeeRate }"></ng-container></small></i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- MAX COST -->
|
<!-- MAX COST -->
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<tr class="group-first">
|
<tr class="group-first">
|
||||||
<td class="item">
|
<td class="item">
|
||||||
<b style="background-color: #105fb0;" class="p-1 pl-0">Maximum acceleration cost</b>
|
<b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
<span style="background-color: #105fb0" class="p-1 pl-0">
|
<span style="background-color: var(--primary)" class="p-1 pl-0">
|
||||||
{{ maxCost | number }}
|
{{ maxCost | number }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -195,7 +185,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="info group-last">
|
<tr class="info group-last">
|
||||||
<td class="info" colspan=3>
|
<td class="info" colspan=3>
|
||||||
<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>
|
<i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: (estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize }"></ng-container></small></i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -203,9 +193,7 @@
|
|||||||
<!-- USER BALANCE -->
|
<!-- USER BALANCE -->
|
||||||
<ng-container *ngIf="isLoggedIn() && estimate.userBalance < maxCost">
|
<ng-container *ngIf="isLoggedIn() && estimate.userBalance < maxCost">
|
||||||
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||||
<td class="item">
|
<td class="item" i18n="accelerator.available-balance">Available balance</td>
|
||||||
Available balance
|
|
||||||
</td>
|
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
{{ estimate.userBalance | number }}
|
{{ estimate.userBalance | number }}
|
||||||
</td>
|
</td>
|
||||||
@@ -224,7 +212,7 @@
|
|||||||
<td class="item"></td>
|
<td class="item"></td>
|
||||||
<td class="amt"></td>
|
<td class="amt"></td>
|
||||||
<td class="units d-flex">
|
<td class="units d-flex">
|
||||||
<a [routerLink]="['/login']" [queryParams]="{redirectTo: '/tx/' + tx.txid + '#accelerate'}" class="btn btn-purple flex-grow-1">Login</a>
|
<a [routerLink]="['/login']" [queryParams]="{redirectTo: '/tx/' + tx.txid + '#accelerate'}" class="btn btn-purple flex-grow-1" i18n="shared.sign-in">Sign In</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -233,7 +221,7 @@
|
|||||||
<td class="item"></td>
|
<td class="item"></td>
|
||||||
<td class="amt"></td>
|
<td class="amt"></td>
|
||||||
<td class="units d-flex">
|
<td class="units d-flex">
|
||||||
<a [href]="'https://mempool.space/tx/' + tx.txid + '#accelerate'" class="btn btn-purple flex-grow-1">Accelerate on mempool.space</a>
|
<a [href]="'https://mempool.space/tx/' + tx.txid + '#accelerate'" class="btn btn-purple flex-grow-1" i18n="accelerator.accelerate-on-mempoolspace">Accelerate on mempool.space</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -245,7 +233,7 @@
|
|||||||
<div class="row mb-3" *ngIf="isLoggedIn()">
|
<div class="row mb-3" *ngIf="isLoggedIn()">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess">
|
<div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess">
|
||||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
|
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()" i18n="transaction.accelerate|Accelerate button label">Accelerate</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -257,4 +245,6 @@
|
|||||||
<ng-template #loadingEstimate>
|
<ng-template #loadingEstimate>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<br>
|
<br>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #acceleratedTo let-i i18n="accelerator.accelerated-to-description">If your tx is accelerated to ~{{ i | number : '1.0-0' }} sat/vB</ng-template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
.fee-card {
|
.fee-card {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
|
|
||||||
.feerate {
|
.feerate {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.feerate.active {
|
.feerate.active {
|
||||||
background-color: #105fb0 !important;
|
background-color: var(--primary) !important;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
border: 1px solid #007fff !important;
|
border: 1px solid #007fff !important;
|
||||||
}
|
}
|
||||||
@@ -109,4 +109,8 @@
|
|||||||
|
|
||||||
.item {
|
.item {
|
||||||
white-space: initial;
|
white-space: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-background {
|
||||||
|
background-color: var(--bg);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
|
||||||
import { Subscription, catchError, of, tap } from 'rxjs';
|
import { Subscription, catchError, of, tap } from 'rxjs';
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
import { Transaction } from '../../interfaces/electrs.interface';
|
import { Transaction } from '../../interfaces/electrs.interface';
|
||||||
@@ -57,6 +56,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
defaultBid = 0;
|
defaultBid = 0;
|
||||||
maxCost = 0;
|
maxCost = 0;
|
||||||
userBid = 0;
|
userBid = 0;
|
||||||
|
accelerationUUID: string;
|
||||||
selectFeeRateIndex = 1;
|
selectFeeRateIndex = 1;
|
||||||
isMobile: boolean = window.innerWidth <= 767.98;
|
isMobile: boolean = window.innerWidth <= 767.98;
|
||||||
user: any = undefined;
|
user: any = undefined;
|
||||||
@@ -69,7 +69,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private audioService: AudioService,
|
private audioService: AudioService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) { }
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.estimateSubscription) {
|
if (this.estimateSubscription) {
|
||||||
@@ -77,13 +78,17 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.accelerationUUID = window.crypto.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (changes.scrollEvent) {
|
if (changes.scrollEvent) {
|
||||||
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngAfterViewInit() {
|
||||||
this.user = this.storageService.getAuth()?.user ?? null;
|
this.user = this.storageService.getAuth()?.user ?? null;
|
||||||
|
|
||||||
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
|
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
|
||||||
@@ -135,6 +140,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
if (!this.error) {
|
if (!this.error) {
|
||||||
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.onScroll();
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -188,7 +197,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
this.accelerationSubscription = this.servicesApiService.accelerate$(
|
this.accelerationSubscription = this.servicesApiService.accelerate$(
|
||||||
this.tx.txid,
|
this.tx.txid,
|
||||||
this.userBid
|
this.userBid,
|
||||||
|
this.accelerationUUID
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.audioService.playSound('ascend-chime-cartoon');
|
this.audioService.playSound('ascend-chime-cartoon');
|
||||||
@@ -216,4 +226,15 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
onResize(): void {
|
onResize(): void {
|
||||||
this.isMobile = window.innerWidth <= 767.98;
|
this.isMobile = window.innerWidth <= 767.98;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@HostListener('window:scroll', ['$event']) // for window scroll events
|
||||||
|
onScroll() {
|
||||||
|
if (this.estimate) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.onScroll();
|
||||||
|
}, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class.chart]="!widget" [class.chart-widget]="widget" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ h5 {
|
|||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||||
import { EChartsOption } from '../../../graphs/echarts';
|
import { EChartsOption } from '../../../graphs/echarts';
|
||||||
import { Observable, Subscription, combineLatest, fromEvent, share } from 'rxjs';
|
import { Observable, Subject, Subscription, combineLatest, fromEvent, merge, share } from 'rxjs';
|
||||||
import { startWith, switchMap, tap } from 'rxjs/operators';
|
import { startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { SeoService } from '../../../services/seo.service';
|
import { SeoService } from '../../../services/seo.service';
|
||||||
import { formatNumber } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
@@ -27,11 +27,12 @@ import { StateService } from '../../../services/state.service';
|
|||||||
`],
|
`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() widget: boolean = false;
|
@Input() widget: boolean = false;
|
||||||
@Input() height: number = 300;
|
@Input() height: number = 300;
|
||||||
@Input() right: number | string = 45;
|
@Input() right: number | string = 45;
|
||||||
@Input() left: number | string = 75;
|
@Input() left: number | string = 75;
|
||||||
|
@Input() period: '3d' | '1w' | '1m' = '1w';
|
||||||
@Input() accelerations$: Observable<Acceleration[]>;
|
@Input() accelerations$: Observable<Acceleration[]>;
|
||||||
|
|
||||||
miningWindowPreference: string;
|
miningWindowPreference: string;
|
||||||
@@ -47,9 +48,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
isLoading = true;
|
isLoading = true;
|
||||||
formatNumber = formatNumber;
|
formatNumber = formatNumber;
|
||||||
timespan = '';
|
timespan = '';
|
||||||
|
periodSubject$: Subject<'3d' | '1w' | '1m'> = new Subject();
|
||||||
chartInstance: any = undefined;
|
chartInstance: any = undefined;
|
||||||
|
|
||||||
currency: string;
|
|
||||||
daysAvailable: number = 0;
|
daysAvailable: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -63,17 +63,16 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1w' });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue('1y');
|
this.radioGroupForm.controls.dateSpan.setValue('1w');
|
||||||
this.currency = 'USD';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.widget) {
|
if (this.widget) {
|
||||||
this.miningWindowPreference = '3m';
|
this.miningWindowPreference = this.period;
|
||||||
} else {
|
} else {
|
||||||
this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`);
|
this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`);
|
||||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('3m');
|
this.miningWindowPreference = this.miningService.getDefaultTimespan('1w');
|
||||||
}
|
}
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||||
@@ -84,8 +83,12 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.aggregatedHistory$ = combineLatest([
|
this.aggregatedHistory$ = combineLatest([
|
||||||
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
merge(
|
||||||
startWith(this.radioGroupForm.controls.dateSpan.value),
|
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
||||||
|
startWith(this.radioGroupForm.controls.dateSpan.value),
|
||||||
|
),
|
||||||
|
this.periodSubject$
|
||||||
|
).pipe(
|
||||||
switchMap((timespan) => {
|
switchMap((timespan) => {
|
||||||
if (!this.widget) {
|
if (!this.widget) {
|
||||||
this.storageService.setValue('miningWindowPreference', timespan);
|
this.storageService.setValue('miningWindowPreference', timespan);
|
||||||
@@ -110,6 +113,12 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
this.aggregatedHistory$.subscribe();
|
this.aggregatedHistory$.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes.period) {
|
||||||
|
this.periodSubject$.next(this.period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prepareChartOptions(data) {
|
prepareChartOptions(data) {
|
||||||
let title: object;
|
let title: object;
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
@@ -221,7 +230,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="accelerator.success-rate">Success Rate</h5>
|
<h5 class="card-title" i18n="accelerator.total-vsize">Total vSize</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div>{{ stats.successRate.toFixed(2) }} %</div>
|
<div [innerHTML]="'‎' + (stats.totalVsize | vbytes: 2)"></div>
|
||||||
<div class="symbol" i18n="accelerator.mined-next-block">mined</div>
|
<div class="symbol">{{ (stats.totalVsize / (1_000_000 * blocksInPeriod) * 100).toFixed(2) }}% <span i18n="accelerator.percent-of-blocks"> of blocks</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="accelerator.success-rate">Success Rate</h5>
|
<h5 class="card-title" i18n="accelerator.total-vsize">Total vSize</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
@media (min-width: 376px) {
|
@media (min-width: 376px) {
|
||||||
margin: 0 auto 0px;
|
margin: 0 auto 0px;
|
||||||
}
|
}
|
||||||
&:first-child{
|
&:last-child{
|
||||||
display: none;
|
display: none;
|
||||||
@media (min-width: 485px) {
|
@media (min-width: 485px) {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.card-text span {
|
.card-text span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ export type AccelerationStats = {
|
|||||||
totalRequested: number;
|
totalRequested: number;
|
||||||
totalBidBoost: number;
|
totalBidBoost: number;
|
||||||
successRate: number;
|
successRate: number;
|
||||||
|
totalVsize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -14,14 +15,35 @@ export type AccelerationStats = {
|
|||||||
styleUrls: ['./acceleration-stats.component.scss'],
|
styleUrls: ['./acceleration-stats.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AccelerationStatsComponent implements OnInit {
|
export class AccelerationStatsComponent implements OnInit, OnChanges {
|
||||||
|
@Input() timespan: '3d' | '1w' | '1m' = '1w';
|
||||||
accelerationStats$: Observable<AccelerationStats>;
|
accelerationStats$: Observable<AccelerationStats>;
|
||||||
|
blocksInPeriod: number = 7 * 144;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private servicesApiService: ServicesApiServices
|
private servicesApiService: ServicesApiServices
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.accelerationStats$ = this.servicesApiService.getAccelerationStats$();
|
this.updateStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.updateStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats(): void {
|
||||||
|
this.accelerationStats$ = this.servicesApiService.getAccelerationStats$({ timeframe: this.timespan });
|
||||||
|
switch (this.timespan) {
|
||||||
|
case '3d':
|
||||||
|
this.blocksInPeriod = 3 * 144;
|
||||||
|
break;
|
||||||
|
case '1w':
|
||||||
|
this.blocksInPeriod = 7 * 144;
|
||||||
|
break;
|
||||||
|
case '1m':
|
||||||
|
this.blocksInPeriod = 30 * 144;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="container-lg widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
<div class="container-lg widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
||||||
<h1 *ngIf="!widget" class="float-left" i18n="master-page.blocks">Accelerations</h1>
|
<h1 *ngIf="!widget" class="float-left" i18n="accelerator.accelerations">Accelerations</h1>
|
||||||
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
|
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
@@ -9,15 +9,15 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<th class="txid text-left" i18n="dashboard.latest-transactions.txid">TXID</th>
|
<th class="txid text-left" i18n="dashboard.latest-transactions.txid">TXID</th>
|
||||||
<ng-container *ngIf="pending">
|
<ng-container *ngIf="pending">
|
||||||
<th class="fee-rate text-right" i18n="transaction.fee|Transaction fee">Fee Rate</th>
|
<th class="fee-rate text-right" i18n="transaction.fee-rate|Transaction fee rate">Fee rate</th>
|
||||||
<th class="bid text-right" i18n="transaction.fee|Transaction fee">Acceleration Bid</th>
|
<th class="bid text-right" i18n="accelerator.bid">Bid</th>
|
||||||
<th class="time text-right" i18n="accelerator.block">Requested</th>
|
<th class="time text-right" i18n="accelerator.requested">Requested</th>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!pending">
|
<ng-container *ngIf="!pending">
|
||||||
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
||||||
<th class="block text-right" i18n="accelerator.block">Block</th>
|
<th class="block text-right" i18n="shared.block-title">Block</th>
|
||||||
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
||||||
<th class="date text-right" i18n="" *ngIf="!this.widget">Requested</th>
|
<th class="date text-right" i18n="accelerator.requested" *ngIf="!this.widget">Requested</th>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="accelerations; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
<tbody *ngIf="accelerations; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||||
@@ -39,10 +39,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!pending">
|
<ng-container *ngIf="!pending">
|
||||||
<td *ngIf="acceleration.feePaid" class="fee text-right">
|
<td *ngIf="acceleration.boost != null" class="fee text-right">
|
||||||
{{ (acceleration.boost) | number }} <span class="symbol" i18n="shared.sat|sat">sat</span>
|
{{ acceleration.boost | number }} <span class="symbol" i18n="shared.sat|sat">sat</span>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!acceleration.feePaid" class="fee text-right">
|
<td *ngIf="acceleration.boost == null" class="fee text-right">
|
||||||
~
|
~
|
||||||
</td>
|
</td>
|
||||||
<td class="block text-right">
|
<td class="block text-right">
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ tr, td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
background-color: #2d3348;
|
background-color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.txid {
|
.txid {
|
||||||
@@ -148,7 +148,7 @@ tr, td, th {
|
|||||||
|
|
||||||
.tooltip-custom .tooltiptext {
|
.tooltip-custom .tooltiptext {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
color: #fff;
|
color: var(--fg);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class AccelerationsListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const acc of accelerations) {
|
for (const acc of accelerations) {
|
||||||
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
|
acc.boost = acc.boostCost != null ? acc.boostCost : acc.bidBoost;
|
||||||
}
|
}
|
||||||
if (this.widget) {
|
if (this.widget) {
|
||||||
return of(accelerations.slice(0, 6));
|
return of(accelerations.slice(0, 6));
|
||||||
|
|||||||
@@ -22,12 +22,26 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="main-title">
|
<div class="main-title">
|
||||||
<span [attr.data-cy]="'acceleration-stats'" i18n="accelerator.acceleration-stats">Acceleration stats</span>
|
<span [attr.data-cy]="'acceleration-stats'" i18n="accelerator.acceleration-stats">Acceleration stats</span>
|
||||||
<span style="font-size: xx-small" i18n="mining.3-months">(3 months)</span>
|
@switch (timespan) {
|
||||||
|
@case ('1w') {
|
||||||
|
<span style="font-size: xx-small" i18n="mining.1-week">(1 week)</span>
|
||||||
|
}
|
||||||
|
@case ('1m') {
|
||||||
|
<span style="font-size: xx-small" i18n="mining.1-month">(1 month)</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-wrapper">
|
<div class="card-wrapper">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body more-padding">
|
<div class="card-body more-padding">
|
||||||
<app-acceleration-stats></app-acceleration-stats>
|
<app-acceleration-stats [timespan]="timespan"></app-acceleration-stats>
|
||||||
|
<div class="widget-toggler">
|
||||||
|
<a href="" (click)="setTimespan('1w')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': timespan === '1w'}"><small>1w</small></a>
|
||||||
|
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
||||||
|
<a href="" (click)="setTimespan('1m')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': timespan === '1m'}"><small>1m</small></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,9 +52,9 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
||||||
<a class="title-link" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="dashboard.mempool-goggles-accelerations">Mempool Goggles: Accelerations</h5>
|
<h5 class="card-title d-inline">Mempool Goggles™ : <ng-container i18n="accelerator.accelerations">Accelerations</ng-container></h5>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: #4a68b9"></fa-icon>
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<div class="mempool-block-wrapper" *ngIf="webGlEnabled">
|
<div class="mempool-block-wrapper" *ngIf="webGlEnabled">
|
||||||
<app-mempool-block-overview [index]="0" [overrideColors]="getAcceleratorColor"></app-mempool-block-overview>
|
<app-mempool-block-overview [index]="0" [overrideColors]="getAcceleratorColor"></app-mempool-block-overview>
|
||||||
@@ -59,6 +73,7 @@
|
|||||||
[height]="graphHeight"
|
[height]="graphHeight"
|
||||||
[attr.data-cy]="'acceleration-fees'"
|
[attr.data-cy]="'acceleration-fees'"
|
||||||
[widget]=true
|
[widget]=true
|
||||||
|
[period]="timespan"
|
||||||
></app-acceleration-fees-graph>
|
></app-acceleration-fees-graph>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1"><a [attr.data-cy]="'acceleration-fees-view-more'" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div>
|
<div class="mt-1"><a [attr.data-cy]="'acceleration-fees-view-more'" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div>
|
||||||
@@ -85,7 +100,7 @@
|
|||||||
<a class="title-link" href="" [routerLink]="['/acceleration/list' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/acceleration/list' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="dashboard.recent-accelerations">Recent Accelerations</h5>
|
<h5 class="card-title d-inline" i18n="dashboard.recent-accelerations">Recent Accelerations</h5>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: #4a68b9"></fa-icon>
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<app-accelerations-list [attr.data-cy]="'recent-accelerations'" [widget]=true [accelerations$]="minedAccelerations$"></app-accelerations-list>
|
<app-accelerations-list [attr.data-cy]="'recent-accelerations'" [widget]=true [accelerations$]="minedAccelerations$"></app-accelerations-list>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-card {
|
.graph-card {
|
||||||
@@ -29,10 +29,10 @@
|
|||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
}
|
||||||
.card-title > a {
|
.card-title > a {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body.pool-ranking {
|
.card-body.pool-ranking {
|
||||||
@@ -172,4 +172,20 @@
|
|||||||
max-height: 430px;
|
max-height: 430px;
|
||||||
max-width: 430px;
|
max-width: 430px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-toggler {
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: 3px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggler-option {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive {
|
||||||
|
color: #ffffff66;
|
||||||
}
|
}
|
||||||
@@ -8,12 +8,15 @@ import { Observable, catchError, combineLatest, distinctUntilChanged, interval,
|
|||||||
import { Color } from '../../block-overview-graph/sprite-types';
|
import { Color } from '../../block-overview-graph/sprite-types';
|
||||||
import { hexToColor } from '../../block-overview-graph/utils';
|
import { hexToColor } from '../../block-overview-graph/utils';
|
||||||
import TxView from '../../block-overview-graph/tx-view';
|
import TxView from '../../block-overview-graph/tx-view';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../../app.constants';
|
import { feeLevels, defaultMempoolFeeColors, contrastMempoolFeeColors } from '../../../app.constants';
|
||||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||||
import { detectWebGL } from '../../../shared/graphs.utils';
|
import { detectWebGL } from '../../../shared/graphs.utils';
|
||||||
|
import { AudioService } from '../../../services/audio.service';
|
||||||
|
import { ThemeService } from '../../../services/theme.service';
|
||||||
|
|
||||||
const acceleratedColor: Color = hexToColor('8F5FF6');
|
const acceleratedColor: Color = hexToColor('8F5FF6');
|
||||||
const normalColors = mempoolFeeColors.map(hex => hexToColor(hex + '5F'));
|
const normalColors = defaultMempoolFeeColors.map(hex => hexToColor(hex + '5F'));
|
||||||
|
const contrastColors = contrastMempoolFeeColors.map(hex => hexToColor(hex.slice(0,6) + '5F'));
|
||||||
|
|
||||||
interface AccelerationBlock extends BlockExtended {
|
interface AccelerationBlock extends BlockExtended {
|
||||||
accelerationCount: number,
|
accelerationCount: number,
|
||||||
@@ -32,14 +35,19 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
minedAccelerations$: Observable<Acceleration[]>;
|
minedAccelerations$: Observable<Acceleration[]>;
|
||||||
loadingBlocks: boolean = true;
|
loadingBlocks: boolean = true;
|
||||||
webGlEnabled = true;
|
webGlEnabled = true;
|
||||||
|
seen: Set<string> = new Set();
|
||||||
|
firstLoad = true;
|
||||||
|
timespan: '3d' | '1w' | '1m' = '1w';
|
||||||
|
|
||||||
graphHeight: number = 300;
|
graphHeight: number = 300;
|
||||||
|
theme: ThemeService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private ogService: OpenGraphService,
|
private ogService: OpenGraphService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private serviceApiServices: ServicesApiServices,
|
private serviceApiServices: ServicesApiServices,
|
||||||
|
private audioService: AudioService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
@Inject(PLATFORM_ID) private platformId: Object,
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {
|
) {
|
||||||
@@ -61,6 +69,15 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
tap(accelerations => {
|
||||||
|
if (!this.firstLoad && accelerations.some(acc => !this.seen.has(acc.txid))) {
|
||||||
|
this.audioService.playSound('bright-harmony');
|
||||||
|
}
|
||||||
|
for(const acc of accelerations) {
|
||||||
|
this.seen.add(acc.txid);
|
||||||
|
}
|
||||||
|
this.firstLoad = false;
|
||||||
|
}),
|
||||||
share(),
|
share(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,15 +120,15 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
switchMap(([accelerations, blocks]) => {
|
switchMap(([accelerations, blocks]) => {
|
||||||
const blockMap = {};
|
const blockMap = {};
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
blockMap[block.id] = block;
|
blockMap[block.height] = block;
|
||||||
}
|
}
|
||||||
const accelerationsByBlock: { [ hash: string ]: Acceleration[] } = {};
|
const accelerationsByBlock: { [ height: number ]: Acceleration[] } = {};
|
||||||
for (const acceleration of accelerations) {
|
for (const acceleration of accelerations) {
|
||||||
if (['completed_provisional', 'failed_provisional', 'completed'].includes(acceleration.status) && acceleration.pools.includes(blockMap[acceleration.blockHash]?.extras.pool.id)) {
|
if (['completed_provisional', 'failed_provisional', 'completed'].includes(acceleration.status) && acceleration.pools.includes(blockMap[acceleration.blockHeight]?.extras.pool.id)) {
|
||||||
if (!accelerationsByBlock[acceleration.blockHash]) {
|
if (!accelerationsByBlock[acceleration.blockHeight]) {
|
||||||
accelerationsByBlock[acceleration.blockHash] = [];
|
accelerationsByBlock[acceleration.blockHeight] = [];
|
||||||
}
|
}
|
||||||
accelerationsByBlock[acceleration.blockHash].push(acceleration);
|
accelerationsByBlock[acceleration.blockHeight].push(acceleration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return of(blocks.slice(0, 6).map(block => {
|
return of(blocks.slice(0, 6).map(block => {
|
||||||
@@ -128,10 +145,15 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
||||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
||||||
return normalColors[feeLevelIndex] || normalColors[mempoolFeeColors.length - 1];
|
return this.theme.theme === 'contrast' ? contrastColors[feeLevelIndex] || contrastColors[contrastColors.length - 1] : normalColors[feeLevelIndex] || normalColors[normalColors.length - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimespan(timespan): boolean {
|
||||||
|
this.timespan = timespan;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(): void {
|
onResize(): void {
|
||||||
if (window.innerWidth >= 992) {
|
if (window.innerWidth >= 992) {
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="accelerator.total-vsize">Total Vsize</h5>
|
<h5 class="card-title" i18n="accelerator.total-vsize">Total vSize</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div [innerHTML]="'‎' + (stats.totalVsize * 4 | vbytes: 2)"></div>
|
<div [innerHTML]="'‎' + (stats.totalVsize | vbytes: 2)"></div>
|
||||||
<div class="symbol">{{ (stats.totalVsize / 1_000_000 * 100).toFixed(2) }}% <span i18n="accelerator.percent-of-next-block"> of next block</span></div>
|
<div class="symbol">{{ (stats.totalVsize / 1_000_000 * 100).toFixed(2) }}% <span i18n="accelerator.percent-of-block"> of block</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="accelerator.total-vsize">Total Vsize</h5>
|
<h5 class="card-title" i18n="accelerator.total-vsize">Total vSize</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.card-title {
|
.card-title {
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
@media (min-width: 376px) {
|
@media (min-width: 376px) {
|
||||||
margin: 0 auto 0px;
|
margin: 0 auto 0px;
|
||||||
}
|
}
|
||||||
&:first-child{
|
&:last-child{
|
||||||
display: none;
|
display: none;
|
||||||
@media (min-width: 485px) {
|
@media (min-width: 485px) {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.card-text span {
|
.card-text span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ChainStats } from '../../interfaces/electrs.interface';
|
|||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-address-graph',
|
selector: 'app-address-graph',
|
||||||
@@ -46,6 +47,7 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private amountShortenerPipe: AmountShortenerPipe,
|
private amountShortenerPipe: AmountShortenerPipe,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@@ -111,7 +113,7 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
: `${data.length} transactions`;
|
: `${data.length} transactions`;
|
||||||
const date = new Date(data[0].data[0]).toLocaleTimeString(this.locale, { year: 'numeric', month: 'short', day: 'numeric' });
|
const date = new Date(data[0].data[0]).toLocaleTimeString(this.locale, { year: 'numeric', month: 'short', day: 'numeric' });
|
||||||
const val = data.reduce((total, d) => total + d.data[2].value, 0);
|
const val = data.reduce((total, d) => total + d.data[2].value, 0);
|
||||||
const color = val === 0 ? '' : (val > 0 ? '#1a9436' : '#dc3545');
|
const color = val === 0 ? '' : (val > 0 ? 'var(--green)' : 'var(--red)');
|
||||||
const symbol = val > 0 ? '+' : '';
|
const symbol = val > 0 ? '+' : '';
|
||||||
return `
|
return `
|
||||||
<div>
|
<div>
|
||||||
@@ -122,7 +124,7 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
</div>
|
</div>
|
||||||
<span>${date}</span>
|
<span>${date}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
@@ -159,7 +161,7 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: $localize`Balance:Balance`,
|
name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`,
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 8,
|
symbolSize: 8,
|
||||||
@@ -178,7 +180,7 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
|
|
||||||
onChartClick(e) {
|
onChartClick(e) {
|
||||||
if (this.hoverData?.length && this.hoverData[0]?.[2]?.txid) {
|
if (this.hoverData?.length && this.hoverData[0]?.[2]?.txid) {
|
||||||
this.router.navigate(['/tx/', this.hoverData[0][2].txid]);
|
this.router.navigate([this.relativeUrlPipe.transform('/tx/'), this.hoverData[0][2].txid]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.frame {
|
.frame {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #24273e;
|
background: var(--box-bg);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
height: calc(100% + 60px);
|
height: calc(100% + 60px);
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: #FFF;
|
background-color: var(--fg);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: #FFF;
|
background-color: var(--fg);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
retryLoadMore = false;
|
retryLoadMore = false;
|
||||||
error: any;
|
error: any;
|
||||||
mainSubscription: Subscription;
|
mainSubscription: Subscription;
|
||||||
|
mempoolTxSubscription: Subscription;
|
||||||
|
mempoolRemovedTxSubscription: Subscription;
|
||||||
|
blockTxSubscription: Subscription;
|
||||||
addressLoadingStatus$: Observable<number>;
|
addressLoadingStatus$: Observable<number>;
|
||||||
addressInfo: null | AddressInformation = null;
|
addressInfo: null | AddressInformation = null;
|
||||||
|
|
||||||
@@ -179,17 +182,17 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
this.isLoadingAddress = false;
|
this.isLoadingAddress = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.stateService.mempoolTransactions$
|
this.mempoolTxSubscription = this.stateService.mempoolTransactions$
|
||||||
.subscribe(tx => {
|
.subscribe(tx => {
|
||||||
this.addTransaction(tx);
|
this.addTransaction(tx);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.stateService.mempoolRemovedTransactions$
|
this.mempoolRemovedTxSubscription = this.stateService.mempoolRemovedTransactions$
|
||||||
.subscribe(tx => {
|
.subscribe(tx => {
|
||||||
this.removeTransaction(tx);
|
this.removeTransaction(tx);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.stateService.blockTransactions$
|
this.blockTxSubscription = this.stateService.blockTransactions$
|
||||||
.subscribe((transaction) => {
|
.subscribe((transaction) => {
|
||||||
const tx = this.transactions.find((t) => t.txid === transaction.txid);
|
const tx = this.transactions.find((t) => t.txid === transaction.txid);
|
||||||
if (tx) {
|
if (tx) {
|
||||||
@@ -295,6 +298,9 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.mainSubscription.unsubscribe();
|
this.mainSubscription.unsubscribe();
|
||||||
|
this.mempoolTxSubscription.unsubscribe();
|
||||||
|
this.mempoolRemovedTxSubscription.unsubscribe();
|
||||||
|
this.blockTxSubscription.unsubscribe();
|
||||||
this.websocketService.stopTrackingAddress();
|
this.websocketService.stopTrackingAddress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@
|
|||||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
||||||
<span i18n="shared.confidential">Confidential</span>
|
<span i18n="shared.confidential">Confidential</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #default>
|
<ng-template #default>‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis / 100000000 | number : digitsInfo }}
|
||||||
‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis / 100000000 | number : digitsInfo }}
|
|
||||||
<span class="symbol"><ng-template [ngIf]="network === 'liquid' && !forceBtc">L-</ng-template>
|
<span class="symbol"><ng-template [ngIf]="network === 'liquid' && !forceBtc">L-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
.green-color {
|
.green-color {
|
||||||
color: #3bcc49;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: #FFF;
|
background-color: var(--fg);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #1d1f31;
|
background-color: var(--bg);
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<ngb-pagination [collectionSize]="assets.length" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="ellipses"></ngb-pagination>
|
<ngb-pagination class="pagination-container" [collectionSize]="assets.length" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="ellipses"></ngb-pagination>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -95,12 +95,12 @@
|
|||||||
}
|
}
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #4a68b9;
|
color: var(--title-fg);
|
||||||
}
|
}
|
||||||
.card-text {
|
.card-text {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
span {
|
span {
|
||||||
color: #ffffff66;
|
color: var(--transparent-fg);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -309,7 +309,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -376,7 +376,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -214,7 +214,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -305,7 +305,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.any-mode]="filterMode === 'or'" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
|
<div class="block-filters" [class.filters-active]="activeFilters.length > 0" [class.any-mode]="filterMode === 'or'" [class.menu-open]="menuOpen" [class.small]="cssWidth < 500" [class.vsmall]="cssWidth < 400" [class.tiny]="cssWidth < 200">
|
||||||
<a *ngIf="menuOpen" [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-mempool-goggles-work" class="info-badges" i18n-ngbTooltip="Mempool Goggles tooltip" ngbTooltip="select filter categories to highlight matching transactions">
|
<a *ngIf="menuOpen" [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-mempool-goggles-work" class="info-badges" i18n-ngbTooltip="Mempool Goggles™ tooltip" ngbTooltip="select filter categories to highlight matching transactions">
|
||||||
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
|
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
|
||||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
|
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="lg"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<button class="menu-toggle" (click)="menuOpen = !menuOpen" title="Mempool Goggles">
|
<button class="menu-toggle" (click)="menuOpen = !menuOpen" title="Mempool Goggles™">
|
||||||
<app-svg-images name="goggles" width="100%" height="100%"></app-svg-images>
|
<app-svg-images name="goggles" width="100%" height="100%"></app-svg-images>
|
||||||
</button>
|
</button>
|
||||||
<div class="active-tags">
|
<div class="active-tags">
|
||||||
@@ -14,14 +14,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
|
<div class="filter-menu" *ngIf="menuOpen && cssWidth > 280">
|
||||||
<h5>Match</h5>
|
<div class="filter-row">
|
||||||
<div class="btn-group btn-group-toggle">
|
<div class="filter-element">
|
||||||
<label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
|
<h5 i18n="mempool-goggles.match">Match</h5>
|
||||||
<input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')">All
|
<div class="btn-group btn-group-toggle">
|
||||||
</label>
|
<label class="btn btn-xs blue mode-toggle" [class.active]="filterMode === 'and'">
|
||||||
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
<input type="radio" [value]="'all'" fragment="all" (click)="setFilterMode('and')"><ng-container i18n>All</ng-container>
|
||||||
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')">Any
|
</label>
|
||||||
</label>
|
<label class="btn btn-xs green mode-toggle" [class.active]="filterMode === 'or'">
|
||||||
|
<input type="radio" [value]="'any'" fragment="any" (click)="setFilterMode('or')"><ng-container i18n="mempool-goggles.any">Any</ng-container>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-element">
|
||||||
|
<h5 i18n="mempool-goggles.tint">Tint</h5>
|
||||||
|
<div class="btn-group btn-group-toggle">
|
||||||
|
<label class="btn btn-xs yellow mode-toggle" [class.active]="gradientMode === 'fee'">
|
||||||
|
<input type="radio" [value]="'fee'" fragment="classic" (click)="setGradientMode('fee')"><ng-container i18n="mempool-goggles.classic">Classic</ng-container>
|
||||||
|
</label>
|
||||||
|
<label class="btn btn-xs blue mode-toggle" [class.active]="gradientMode === 'age'">
|
||||||
|
<input type="radio" [value]="'age'" fragment="age" (click)="setGradientMode('age')"><ng-container i18n="mempool-goggles.age">Age</ng-container>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngFor="let group of filterGroups;">
|
<ng-container *ngFor="let group of filterGroups;">
|
||||||
<h5>{{ group.label }}</h5>
|
<h5>{{ group.label }}</h5>
|
||||||
|
|||||||
@@ -45,6 +45,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-menu {
|
.filter-menu {
|
||||||
|
.filter-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -64,7 +71,7 @@
|
|||||||
.filter-tag {
|
.filter-tag {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
background: #181b2daf;
|
background: #181b2daf;
|
||||||
border: solid 1px #105fb0;
|
border: solid 1px var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
padding: 0.2em 0.5em;
|
padding: 0.2em 0.5em;
|
||||||
@@ -73,15 +80,15 @@
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: #105fb0;
|
background-color: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.any-mode {
|
&.any-mode {
|
||||||
.filter-tag {
|
.filter-tag {
|
||||||
border: solid 1px #1a9436;
|
border: solid 1px var(--success);
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: #1a9436;
|
background-color: var(--success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,15 +114,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.blue {
|
&.blue {
|
||||||
border: solid 1px #105fb0;
|
border: solid 1px var(--primary);
|
||||||
&.active {
|
&.active {
|
||||||
background: #105fb0;
|
background: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.green {
|
&.green {
|
||||||
border: solid 1px #1a9436;
|
border: solid 1px var(--success);
|
||||||
&.active {
|
&.active {
|
||||||
background: #1a9436;
|
background: var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.yellow {
|
||||||
|
border: solid 1px #bf7815;
|
||||||
|
&.active {
|
||||||
|
background: #bf7815;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +136,7 @@
|
|||||||
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
|
:host-context(.block-overview-graph:hover) &, &:hover, &:active {
|
||||||
.menu-toggle {
|
.menu-toggle {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core';
|
import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { ActiveFilter, FilterGroups, FilterMode, TransactionFilters } from '../../shared/filters.utils';
|
import { ActiveFilter, FilterGroups, FilterMode, GradientMode, TransactionFilters } from '../../shared/filters.utils';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
activeFilters: string[] = [];
|
activeFilters: string[] = [];
|
||||||
filterFlags: { [key: string]: boolean } = {};
|
filterFlags: { [key: string]: boolean } = {};
|
||||||
filterMode: FilterMode = 'and';
|
filterMode: FilterMode = 'and';
|
||||||
|
gradientMode: GradientMode = 'fee';
|
||||||
menuOpen: boolean = false;
|
menuOpen: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -32,6 +33,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
|
this.filterSubscription = this.stateService.activeGoggles$.subscribe((active: ActiveFilter) => {
|
||||||
this.filterMode = active.mode;
|
this.filterMode = active.mode;
|
||||||
|
this.gradientMode = active.gradient;
|
||||||
for (const key of Object.keys(this.filterFlags)) {
|
for (const key of Object.keys(this.filterFlags)) {
|
||||||
this.filterFlags[key] = false;
|
this.filterFlags[key] = false;
|
||||||
}
|
}
|
||||||
@@ -39,7 +41,7 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.filterFlags[key] = !this.disabledFilters[key];
|
this.filterFlags[key] = !this.disabledFilters[key];
|
||||||
}
|
}
|
||||||
this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
|
this.activeFilters = [...active.filters.filter(key => !this.disabledFilters[key])];
|
||||||
this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters });
|
this.onFilterChanged.emit({ mode: active.mode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,8 +59,14 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
setFilterMode(mode): void {
|
setFilterMode(mode): void {
|
||||||
this.filterMode = mode;
|
this.filterMode = mode;
|
||||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||||
|
}
|
||||||
|
|
||||||
|
setGradientMode(mode): void {
|
||||||
|
this.gradientMode = mode;
|
||||||
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFilter(key): void {
|
toggleFilter(key): void {
|
||||||
@@ -81,8 +89,8 @@ export class BlockFiltersComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.activeFilters = this.activeFilters.filter(f => f != key);
|
this.activeFilters = this.activeFilters.filter(f => f != key);
|
||||||
}
|
}
|
||||||
const booleanFlags = this.getBooleanFlags();
|
const booleanFlags = this.getBooleanFlags();
|
||||||
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters });
|
this.onFilterChanged.emit({ mode: this.filterMode, filters: this.activeFilters, gradient: this.gradientMode });
|
||||||
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters] });
|
this.stateService.activeGoggles$.next({ mode: this.filterMode, filters: [...this.activeFilters], gradient: this.gradientMode });
|
||||||
}
|
}
|
||||||
|
|
||||||
getBooleanFlags(): bigint | null {
|
getBooleanFlags(): bigint | null {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export class BlockHealthGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -178,7 +178,7 @@ export class BlockHealthGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -187,7 +187,7 @@ export class BlockHealthGraphComponent implements OnInit {
|
|||||||
series: data.length === 0 ? undefined : [
|
series: data.length === 0 ? undefined : [
|
||||||
{
|
{
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
name: $localize`Health`,
|
name: $localize`:@@d2bcd3296d2850de762fb943060b7e086a893181:Health`,
|
||||||
data: data.map(health => ({
|
data: data.map(health => ({
|
||||||
value: health[2],
|
value: health[2],
|
||||||
block: health[1],
|
block: health[1],
|
||||||
@@ -290,7 +290,7 @@ export class BlockHealthGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ import TxView from './tx-view';
|
|||||||
import { Color, Position } from './sprite-types';
|
import { Color, Position } from './sprite-types';
|
||||||
import { Price } from '../../services/price.service';
|
import { Price } from '../../services/price.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { ThemeService } from '../../services/theme.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils';
|
import { defaultColorFunction, setOpacity, defaultAuditColors, defaultColors, ageColorFunction, contrastColorFunction, contrastAuditColors, contrastColors } from './utils';
|
||||||
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils';
|
||||||
import { detectWebGL } from '../../shared/graphs.utils';
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
|
|
||||||
const unmatchedOpacity = 0.2;
|
const unmatchedOpacity = 0.2;
|
||||||
const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
|
||||||
const unmatchedAuditFeeColors = defaultAuditFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
|
||||||
const unmatchedMarginalFeeColors = defaultMarginalFeeColors.map(c => setOpacity(c, unmatchedOpacity));
|
|
||||||
const unmatchedAuditColors = {
|
const unmatchedAuditColors = {
|
||||||
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
|
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
|
||||||
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
|
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
|
||||||
@@ -23,6 +21,13 @@ const unmatchedAuditColors = {
|
|||||||
prioritized: setOpacity(defaultAuditColors.prioritized, unmatchedOpacity),
|
prioritized: setOpacity(defaultAuditColors.prioritized, unmatchedOpacity),
|
||||||
accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity),
|
accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity),
|
||||||
};
|
};
|
||||||
|
const unmatchedContrastAuditColors = {
|
||||||
|
censored: setOpacity(contrastAuditColors.censored, unmatchedOpacity),
|
||||||
|
missing: setOpacity(contrastAuditColors.missing, unmatchedOpacity),
|
||||||
|
added: setOpacity(contrastAuditColors.added, unmatchedOpacity),
|
||||||
|
prioritized: setOpacity(contrastAuditColors.prioritized, unmatchedOpacity),
|
||||||
|
accelerated: setOpacity(contrastAuditColors.accelerated, unmatchedOpacity),
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-overview-graph',
|
selector: 'app-block-overview-graph',
|
||||||
@@ -46,6 +51,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
@Input() excludeFilters: string[] = [];
|
@Input() excludeFilters: string[] = [];
|
||||||
@Input() filterFlags: bigint | null = null;
|
@Input() filterFlags: bigint | null = null;
|
||||||
@Input() filterMode: FilterMode = 'and';
|
@Input() filterMode: FilterMode = 'and';
|
||||||
|
@Input() gradientMode: 'fee' | 'age' = 'fee';
|
||||||
@Input() relativeTime: number | null;
|
@Input() relativeTime: number | null;
|
||||||
@Input() blockConversion: Price;
|
@Input() blockConversion: Price;
|
||||||
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
||||||
@@ -55,6 +61,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
@ViewChild('blockCanvas')
|
@ViewChild('blockCanvas')
|
||||||
canvas: ElementRef<HTMLCanvasElement>;
|
canvas: ElementRef<HTMLCanvasElement>;
|
||||||
|
themeChangedSubscription: Subscription;
|
||||||
|
|
||||||
gl: WebGLRenderingContext;
|
gl: WebGLRenderingContext;
|
||||||
animationFrameRequest: number;
|
animationFrameRequest: number;
|
||||||
@@ -86,6 +93,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
readonly ngZone: NgZone,
|
readonly ngZone: NgZone,
|
||||||
readonly elRef: ElementRef,
|
readonly elRef: ElementRef,
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
private themeService: ThemeService,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize);
|
||||||
@@ -104,6 +112,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
if (this.gl) {
|
if (this.gl) {
|
||||||
this.initCanvas();
|
this.initCanvas();
|
||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
|
this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => {
|
||||||
|
this.scene.setColorFunction(this.getColorFunction());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,21 +132,22 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.setHighlightingEnabled(this.auditHighlighting);
|
this.setHighlightingEnabled(this.auditHighlighting);
|
||||||
}
|
}
|
||||||
if (changes.overrideColor && this.scene) {
|
if (changes.overrideColor && this.scene) {
|
||||||
this.scene.setColorFunction(this.overrideColors);
|
this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode));
|
||||||
}
|
}
|
||||||
if ((changes.filterFlags || changes.showFilters || changes.filterMode)) {
|
if ((changes.filterFlags || changes.showFilters || changes.filterMode || changes.gradientMode)) {
|
||||||
this.setFilterFlags();
|
this.setFilterFlags();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilterFlags(goggle?: ActiveFilter): void {
|
setFilterFlags(goggle?: ActiveFilter): void {
|
||||||
this.filterMode = goggle?.mode || this.filterMode;
|
this.filterMode = goggle?.mode || this.filterMode;
|
||||||
|
this.gradientMode = goggle?.gradient || this.gradientMode;
|
||||||
this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
|
this.activeFilterFlags = goggle?.filters ? toFlags(goggle.filters) : this.filterFlags;
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
if (this.activeFilterFlags != null && this.filtersAvailable) {
|
if (this.activeFilterFlags != null && this.filtersAvailable) {
|
||||||
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags));
|
this.scene.setColorFunction(this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode));
|
||||||
} else {
|
} else {
|
||||||
this.scene.setColorFunction(this.overrideColors);
|
this.scene.setColorFunction(this.getFilterColorFunction(0n, this.gradientMode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.start();
|
this.start();
|
||||||
@@ -149,6 +161,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||||
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||||
|
this.themeChangedSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +225,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
remove = remove.filter(txid => this.scene.txs[txid]);
|
remove = remove.filter(txid => this.scene.txs[txid]);
|
||||||
change = change.filter(tx => this.scene.txs[tx.txid]);
|
change = change.filter(tx => this.scene.txs[tx.txid]);
|
||||||
|
|
||||||
|
if (this.gradientMode === 'age') {
|
||||||
|
this.scene.updateAllColors();
|
||||||
|
}
|
||||||
this.scene.update(add, remove, change, direction, resetLayout);
|
this.scene.update(add, remove, change, direction, resetLayout);
|
||||||
this.start();
|
this.start();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
@@ -291,7 +307,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.start();
|
this.start();
|
||||||
} else {
|
} else {
|
||||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
|
||||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray,
|
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, theme: this.themeService,
|
||||||
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset,
|
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset,
|
||||||
colorFunction: this.getColorFunction() });
|
colorFunction: this.getColorFunction() });
|
||||||
this.start();
|
this.start();
|
||||||
@@ -547,27 +563,41 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
|
|
||||||
getColorFunction(): ((tx: TxView) => Color) {
|
getColorFunction(): ((tx: TxView) => Color) {
|
||||||
if (this.filterFlags) {
|
if (this.overrideColors) {
|
||||||
return this.getFilterColorFunction(this.filterFlags);
|
|
||||||
} else if (this.activeFilterFlags) {
|
|
||||||
return this.getFilterColorFunction(this.activeFilterFlags);
|
|
||||||
} else {
|
|
||||||
return this.overrideColors;
|
return this.overrideColors;
|
||||||
|
} else if (this.filterFlags) {
|
||||||
|
return this.getFilterColorFunction(this.filterFlags, this.gradientMode);
|
||||||
|
} else if (this.activeFilterFlags) {
|
||||||
|
return this.getFilterColorFunction(this.activeFilterFlags, this.gradientMode);
|
||||||
|
} else {
|
||||||
|
return this.getFilterColorFunction(0n, this.gradientMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) {
|
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
||||||
return (tx: TxView) => {
|
return (tx: TxView) => {
|
||||||
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
||||||
return defaultColorFunction(tx);
|
if (this.themeService.theme !== 'contrast') {
|
||||||
|
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||||
|
} else {
|
||||||
|
return (gradient === 'age') ? ageColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)) : contrastColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return defaultColorFunction(
|
if (this.themeService.theme !== 'contrast') {
|
||||||
tx,
|
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction(
|
||||||
unmatchedFeeColors,
|
tx,
|
||||||
unmatchedAuditFeeColors,
|
defaultColors.unmatchedfee,
|
||||||
unmatchedMarginalFeeColors,
|
unmatchedAuditColors,
|
||||||
unmatchedAuditColors
|
this.relativeTime || (Date.now() / 1000)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : contrastColorFunction(
|
||||||
|
tx,
|
||||||
|
contrastColors.unmatchedfee,
|
||||||
|
unmatchedContrastAuditColors,
|
||||||
|
this.relativeTime || (Date.now() / 1000)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import { FastVertexArray } from './fast-vertex-array';
|
|||||||
import TxView from './tx-view';
|
import TxView from './tx-view';
|
||||||
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
|
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
|
||||||
import { defaultColorFunction } from './utils';
|
import { defaultColorFunction, contrastColorFunction } from './utils';
|
||||||
|
import { ThemeService } from '../../services/theme.service';
|
||||||
|
|
||||||
export default class BlockScene {
|
export default class BlockScene {
|
||||||
scene: { count: number, offset: { x: number, y: number}};
|
scene: { count: number, offset: { x: number, y: number}};
|
||||||
vertexArray: FastVertexArray;
|
vertexArray: FastVertexArray;
|
||||||
txs: { [key: string]: TxView };
|
txs: { [key: string]: TxView };
|
||||||
getColor: ((tx: TxView) => Color) = defaultColorFunction;
|
getColor: ((tx: TxView) => Color) = defaultColorFunction;
|
||||||
|
theme: ThemeService;
|
||||||
orientation: string;
|
orientation: string;
|
||||||
flip: boolean;
|
flip: boolean;
|
||||||
animationDuration: number = 900;
|
animationDuration: number = 900;
|
||||||
@@ -29,11 +31,11 @@ export default class BlockScene {
|
|||||||
animateUntil = 0;
|
animateUntil = 0;
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
|
|
||||||
constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }:
|
constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }:
|
||||||
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
||||||
) {
|
) {
|
||||||
this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction });
|
this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction });
|
||||||
}
|
}
|
||||||
|
|
||||||
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void {
|
||||||
@@ -67,7 +69,11 @@ export default class BlockScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
||||||
this.getColor = colorFunction || defaultColorFunction;
|
this.theme.theme === 'contrast' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
||||||
|
this.updateAllColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAllColors(): void {
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
if (this.initialised && this.scene) {
|
if (this.initialised && this.scene) {
|
||||||
this.updateColors(performance.now(), 50);
|
this.updateColors(performance.now(), 50);
|
||||||
@@ -193,6 +199,7 @@ export default class BlockScene {
|
|||||||
this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize);
|
this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize);
|
||||||
this.txs[tx.txid].rate = tx.rate;
|
this.txs[tx.txid].rate = tx.rate;
|
||||||
this.txs[tx.txid].dirty = true;
|
this.txs[tx.txid].dirty = true;
|
||||||
|
this.updateColor(this.txs[tx.txid], startTime, 50, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -228,9 +235,9 @@ export default class BlockScene {
|
|||||||
this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value));
|
this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }:
|
private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }:
|
||||||
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
||||||
): void {
|
): void {
|
||||||
this.animationDuration = animationDuration || 1000;
|
this.animationDuration = animationDuration || 1000;
|
||||||
this.configAnimationOffset = animationOffset;
|
this.configAnimationOffset = animationOffset;
|
||||||
@@ -239,7 +246,8 @@ export default class BlockScene {
|
|||||||
this.flip = flip;
|
this.flip = flip;
|
||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
this.highlightingEnabled = highlighting;
|
this.highlightingEnabled = highlighting;
|
||||||
this.getColor = colorFunction || defaultColorFunction;
|
theme.theme === 'contrast' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
||||||
|
this.theme = theme;
|
||||||
|
|
||||||
this.scene = {
|
this.scene = {
|
||||||
count: 0,
|
count: 0,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
import { feeLevels, defaultMempoolFeeColors, contrastMempoolFeeColors } from '../../app.constants';
|
||||||
import { Color } from './sprite-types';
|
import { Color } from './sprite-types';
|
||||||
import TxView from './tx-view';
|
import TxView from './tx-view';
|
||||||
|
|
||||||
@@ -37,36 +37,91 @@ export function setOpacity(color: Color, opacity: number): Color {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ColorPalette {
|
||||||
|
base: Color[],
|
||||||
|
audit: Color[],
|
||||||
|
marginal: Color[],
|
||||||
|
baseLevel: (tx: TxView, rate: number, time: number) => number,
|
||||||
|
}
|
||||||
|
|
||||||
// precomputed colors
|
// precomputed colors
|
||||||
export const defaultFeeColors = mempoolFeeColors.map(hexToColor);
|
const defaultColors: { [key: string]: ColorPalette } = {
|
||||||
export const defaultAuditFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
fee: {
|
||||||
export const defaultMarginalFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
|
base: defaultMempoolFeeColors.map(hexToColor),
|
||||||
|
audit: [],
|
||||||
|
marginal: [],
|
||||||
|
baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for (const key in defaultColors) {
|
||||||
|
const base = defaultColors[key].base;
|
||||||
|
defaultColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||||
|
defaultColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1));
|
||||||
|
defaultColors['unmatched' + key] = {
|
||||||
|
base: defaultColors[key].base.map(c => setOpacity(c, 0.2)),
|
||||||
|
audit: defaultColors[key].audit.map(c => setOpacity(c, 0.2)),
|
||||||
|
marginal: defaultColors[key].marginal.map(c => setOpacity(c, 0.2)),
|
||||||
|
baseLevel: defaultColors[key].baseLevel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { defaultColors as defaultColors };
|
||||||
|
|
||||||
export const defaultAuditColors = {
|
export const defaultAuditColors = {
|
||||||
censored: hexToColor('f344df'),
|
censored: hexToColor('f344df'),
|
||||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||||
added: hexToColor('0099ff'),
|
added: hexToColor('0099ff'),
|
||||||
prioritized: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
prioritized: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||||
accelerated: hexToColor('8F5FF6'),
|
accelerated: hexToColor('8f5ff6'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const contrastColors: { [key: string]: ColorPalette } = {
|
||||||
|
fee: {
|
||||||
|
base: contrastMempoolFeeColors.map(hexToColor),
|
||||||
|
audit: [],
|
||||||
|
marginal: [],
|
||||||
|
baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for (const key in contrastColors) {
|
||||||
|
const base = contrastColors[key].base;
|
||||||
|
contrastColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||||
|
contrastColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1));
|
||||||
|
contrastColors['unmatched' + key] = {
|
||||||
|
base: contrastColors[key].base.map(c => setOpacity(c, 0.2)),
|
||||||
|
audit: contrastColors[key].audit.map(c => setOpacity(c, 0.2)),
|
||||||
|
marginal: contrastColors[key].marginal.map(c => setOpacity(c, 0.2)),
|
||||||
|
baseLevel: contrastColors[key].baseLevel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { contrastColors as contrastColors };
|
||||||
|
|
||||||
|
export const contrastAuditColors = {
|
||||||
|
censored: hexToColor('ffa8ff'),
|
||||||
|
missing: darken(desaturate(hexToColor('ffa8ff'), 0.3), 0.7),
|
||||||
|
added: hexToColor('00bb98'),
|
||||||
|
prioritized: darken(desaturate(hexToColor('00bb98'), 0.3), 0.7),
|
||||||
|
accelerated: hexToColor('8f5ff6'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function defaultColorFunction(
|
export function defaultColorFunction(
|
||||||
tx: TxView,
|
tx: TxView,
|
||||||
feeColors: Color[] = defaultFeeColors,
|
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee,
|
||||||
auditFeeColors: Color[] = defaultAuditFeeColors,
|
auditColors: { [status: string]: Color } = defaultAuditColors,
|
||||||
marginalFeeColors: Color[] = defaultMarginalFeeColors,
|
relativeTime?: number,
|
||||||
auditColors: { [status: string]: Color } = defaultAuditColors
|
|
||||||
): Color {
|
): Color {
|
||||||
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
||||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
const levelIndex = colors.baseLevel(tx, rate, relativeTime || (Date.now() / 1000));
|
||||||
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
|
const levelColor = colors.base[levelIndex] || colors.base[defaultMempoolFeeColors.length - 1];
|
||||||
// Normal mode
|
// Normal mode
|
||||||
if (!tx.scene?.highlightingEnabled) {
|
if (!tx.scene?.highlightingEnabled) {
|
||||||
if (tx.acc) {
|
if (tx.acc) {
|
||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
} else {
|
} else {
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
// Block audit
|
// Block audit
|
||||||
switch(tx.status) {
|
switch(tx.status) {
|
||||||
@@ -75,7 +130,7 @@ export function defaultColorFunction(
|
|||||||
case 'missing':
|
case 'missing':
|
||||||
case 'sigop':
|
case 'sigop':
|
||||||
case 'rbf':
|
case 'rbf':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return colors.marginal[levelIndex] || colors.marginal[defaultMempoolFeeColors.length - 1];
|
||||||
case 'fresh':
|
case 'fresh':
|
||||||
case 'freshcpfp':
|
case 'freshcpfp':
|
||||||
return auditColors.missing;
|
return auditColors.missing;
|
||||||
@@ -84,20 +139,51 @@ export function defaultColorFunction(
|
|||||||
case 'prioritized':
|
case 'prioritized':
|
||||||
return auditColors.prioritized;
|
return auditColors.prioritized;
|
||||||
case 'selected':
|
case 'selected':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return colors.marginal[levelIndex] || colors.marginal[defaultMempoolFeeColors.length - 1];
|
||||||
case 'accelerated':
|
case 'accelerated':
|
||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
case 'found':
|
case 'found':
|
||||||
if (tx.context === 'projected') {
|
if (tx.context === 'projected') {
|
||||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
return colors.audit[levelIndex] || colors.audit[defaultMempoolFeeColors.length - 1];
|
||||||
} else {
|
} else {
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (tx.acc) {
|
if (tx.acc) {
|
||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
} else {
|
} else {
|
||||||
return feeLevelColor;
|
return levelColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function contrastColorFunction(
|
||||||
|
tx: TxView,
|
||||||
|
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = contrastColors.fee,
|
||||||
|
auditColors: { [status: string]: Color } = contrastAuditColors,
|
||||||
|
relativeTime?: number,
|
||||||
|
): Color {
|
||||||
|
return defaultColorFunction(tx, colors, auditColors, relativeTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ageColorFunction(
|
||||||
|
tx: TxView,
|
||||||
|
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee,
|
||||||
|
auditColors: { [status: string]: Color } = defaultAuditColors,
|
||||||
|
relativeTime?: number,
|
||||||
|
theme?: string,
|
||||||
|
): Color {
|
||||||
|
if (tx.acc || tx.status === 'accelerated') {
|
||||||
|
return auditColors.accelerated;
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = theme !== 'contrast' ? defaultColorFunction(tx, colors, auditColors, relativeTime) : contrastColorFunction(tx, colors, auditColors, relativeTime);
|
||||||
|
|
||||||
|
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
||||||
|
return {
|
||||||
|
r: color.r,
|
||||||
|
g: color.g,
|
||||||
|
b: color.b,
|
||||||
|
a: color.a * (1 - ageLevel)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class="block-overview-tooltip"
|
class="block-overview-tooltip"
|
||||||
[class.clickable]="clickable"
|
[class.clickable]="clickable"
|
||||||
[style.visibility]="tx ? 'visible' : 'hidden'"
|
[style.visibility]="tx ? 'visible' : 'hidden'"
|
||||||
[style.left]="tooltipPosition.x + 'px'"
|
[style.left]="getTooltipLeftPosition()"
|
||||||
[style.top]="tooltipPosition.y + 'px'"
|
[style.top]="tooltipPosition.y + 'px'"
|
||||||
>
|
>
|
||||||
<table class="table-fixed">
|
<table class="table-fixed">
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<td class="value"><i><app-time kind="span" [time]="time - relativeTime"></app-time></i></td>
|
<td class="value"><i><app-time kind="span" [time]="time - relativeTime"></app-time></i></td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngSwitchCase="'mined'">
|
<ng-container *ngSwitchCase="'mined'">
|
||||||
<td class="label" i18n="transaction.confirmed-after|Transaction confirmed after">Confirmed</td>
|
<td class="label" i18n="transaction.confirmed|Transaction confirmed state">Confirmed</td>
|
||||||
<td class="value"><i><app-time kind="span" [time]="relativeTime - time"></app-time></i></td>
|
<td class="value"><i><app-time kind="span" [time]="relativeTime - time"></app-time></i></td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -68,15 +68,15 @@
|
|||||||
<td class="value">
|
<td class="value">
|
||||||
<ng-container [ngSwitch]="tx?.status">
|
<ng-container [ngSwitch]="tx?.status">
|
||||||
<span *ngSwitchCase="'found'" class="badge badge-success" i18n="transaction.audit.match">Match</span>
|
<span *ngSwitchCase="'found'" class="badge badge-success" i18n="transaction.audit.match">Match</span>
|
||||||
<span *ngSwitchCase="'censored'" class="badge badge-danger" i18n="transaction.audit.removed">Removed</span>
|
<span *ngSwitchCase="'censored'" class="badge badge-danger" i18n="transaction.audit.removed|Transaction removed state">Removed</span>
|
||||||
<span *ngSwitchCase="'missing'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
<span *ngSwitchCase="'missing'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
||||||
<span *ngSwitchCase="'sigop'" class="badge badge-warning" i18n="transaction.audit.sigop">High sigop count</span>
|
<span *ngSwitchCase="'sigop'" class="badge badge-warning" i18n="transaction.audit.sigop">High sigop count</span>
|
||||||
<span *ngSwitchCase="'fresh'" class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span>
|
<span *ngSwitchCase="'fresh'" class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span>
|
||||||
<span *ngSwitchCase="'freshcpfp'" class="badge badge-warning" i18n="transaction.audit.recently-cpfped">Recently CPFP'd</span>
|
<span *ngSwitchCase="'freshcpfp'" class="badge badge-warning" i18n="transaction.audit.recently-cpfped">Recently CPFP'd</span>
|
||||||
<span *ngSwitchCase="'added'" class="badge badge-warning" i18n="transaction.audit.added">Added</span>
|
<span *ngSwitchCase="'added'" class="badge badge-warning" i18n="tx-features.tag.added|Added">Added</span>
|
||||||
<span *ngSwitchCase="'prioritized'" class="badge badge-warning" i18n="transaction.audit.prioritized">Prioritized</span>
|
<span *ngSwitchCase="'prioritized'" class="badge badge-warning" i18n="tx-features.tag.prioritized|Prioritized">Prioritized</span>
|
||||||
<span *ngSwitchCase="'selected'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
<span *ngSwitchCase="'selected'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
||||||
<span *ngSwitchCase="'rbf'" class="badge badge-warning" i18n="transaction.audit.conflicting">Conflicting</span>
|
<span *ngSwitchCase="'rbf'" class="badge badge-warning" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
|
||||||
<span *ngSwitchCase="'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
|
<span *ngSwitchCase="'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
background: rgba(#11131f, 0.95);
|
background: rgba(#11131f, 0.95);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: #b1b1b1;
|
color: var(--tooltip-grey);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
min-width: 340px;
|
min-width: 340px;
|
||||||
max-width: 340px;
|
max-width: 400px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ th, td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.badge.badge-accelerated {
|
.badge.badge-accelerated {
|
||||||
background-color: #653b9c;
|
background-color: var(--tertiary);
|
||||||
box-shadow: #ad7de57f 0px 0px 12px -2px;
|
box-shadow: #ad7de57f 0px 0px 12px -2px;
|
||||||
color: white;
|
color: white;
|
||||||
animation: acceleratePulse 1s infinite;
|
animation: acceleratePulse 1s infinite;
|
||||||
@@ -41,7 +41,7 @@ th, td {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
row-gap: 0.25em;
|
row-gap: 0.25em;
|
||||||
margin-top: 0.2em;
|
margin-top: 0.2em;
|
||||||
max-width: 100%;
|
max-width: 310px;
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
@@ -51,27 +51,27 @@ th, td {
|
|||||||
|
|
||||||
.filter-tag {
|
.filter-tag {
|
||||||
background: #181b2daf;
|
background: #181b2daf;
|
||||||
border: solid 1px #105fb0;
|
border: solid 1px var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
transition: background-color 300ms;
|
transition: background-color 300ms;
|
||||||
|
|
||||||
&.matching {
|
&.matching {
|
||||||
background-color: #105fb0;
|
background-color: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.any-mode {
|
&.any-mode {
|
||||||
.filter-tag {
|
.filter-tag {
|
||||||
border: solid 1px #1a9436;
|
border: solid 1px var(--success);
|
||||||
&.matching {
|
&.matching {
|
||||||
background-color: #1a9436;
|
background-color: var(--success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes acceleratePulse {
|
@keyframes acceleratePulse {
|
||||||
0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
0% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
||||||
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}
|
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}
|
||||||
100% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
100% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
this.effectiveRate = this.tx.rate;
|
this.effectiveRate = this.tx.rate;
|
||||||
const txFlags = BigInt(this.tx.flags) || 0n;
|
const txFlags = BigInt(this.tx.flags) || 0n;
|
||||||
this.acceleration = this.tx.acc || (txFlags & TransactionFlags.acceleration);
|
this.acceleration = this.tx.acc || (txFlags & TransactionFlags.acceleration);
|
||||||
this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05
|
this.hasEffectiveRate = this.tx.acc || Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05
|
||||||
|| (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n);
|
|| (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n);
|
||||||
this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : [];
|
this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : [];
|
||||||
this.activeFilters = {}
|
this.activeFilters = {}
|
||||||
@@ -96,4 +96,8 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTooltipLeftPosition(): string {
|
||||||
|
return window.innerWidth < 392 ? '-50px' : this.tooltipPosition.x + 'px';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -219,7 +219,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -315,7 +315,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: 'var(--tooltip-grey)',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
@@ -230,7 +230,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'dotted',
|
type: 'dotted',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 0.25,
|
opacity: 0.25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -252,7 +252,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
type: 'solid',
|
type: 'solid',
|
||||||
color: '#ffffff66',
|
color: 'var(--transparent-fg)',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
width: 1,
|
width: 1,
|
||||||
},
|
},
|
||||||
@@ -342,7 +342,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = 40;
|
this.chartOptions.grid.bottom = 40;
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
this.chartOptions.backgroundColor = 'var(--active-bg)';
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
this.chartInstance.setOption(this.chartOptions);
|
||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.block-wrapper {
|
.block-wrapper {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: #181b2d;
|
background: var(--stat-box-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-container {
|
.block-container {
|
||||||
|
|||||||
@@ -53,13 +53,13 @@
|
|||||||
<td i18n="block.miner">Miner</td>
|
<td i18n="block.miner">Miner</td>
|
||||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||||
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
||||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
[class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
{{ block?.extras.pool.name }}
|
{{ block?.extras.pool.name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||||
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
|
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
|
||||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
[class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
{{ block?.extras.pool.name }}
|
{{ block?.extras.pool.name }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
|
|||||||
return of(transactions);
|
return of(transactions);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHash: block.id }) : of([])
|
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -182,13 +182,13 @@
|
|||||||
<td i18n="block.miner">Miner</td>
|
<td i18n="block.miner">Miner</td>
|
||||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
[class]="block.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
{{ block.extras.pool.name }}
|
{{ block.extras.pool.name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||||
<span placement="bottom" class="badge"
|
<span placement="bottom" class="badge"
|
||||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
[class]="block.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||||
{{ block.extras.pool.name }}
|
{{ block.extras.pool.name }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: #FFF;
|
background-color: var(--fg);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -175,9 +175,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #1ad8f4;
|
|
||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
color: #09a3ba;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +252,7 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: #24273e;
|
background: var(--box-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active, &:hover {
|
&.active, &:hover {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, ViewChildren, QueryList, Inject, PLATFORM
|
|||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith } from 'rxjs/operators';
|
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators';
|
||||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
@@ -345,7 +345,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
return of(null);
|
return of(null);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHash: block.id }) : of([])
|
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([])
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -358,11 +358,21 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const acceleratedInBlock = {};
|
const acceleratedInBlock = {};
|
||||||
for (const acc of accelerations) {
|
for (const acc of accelerations) {
|
||||||
acceleratedInBlock[acc.txid] = acc;
|
if (acc.pools?.some(pool => pool === this.block?.extras?.pool.id || pool?.['pool_unique_id'] === this.block?.extras?.pool.id)) {
|
||||||
|
acceleratedInBlock[acc.txid] = acc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const tx of transactions) {
|
for (const tx of transactions) {
|
||||||
if (acceleratedInBlock[tx.txid]) {
|
if (acceleratedInBlock[tx.txid]) {
|
||||||
tx.acc = true;
|
tx.acc = true;
|
||||||
|
const acceleration = acceleratedInBlock[tx.txid];
|
||||||
|
const boostCost = acceleration.boostCost || acceleration.bidBoost;
|
||||||
|
const acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize;
|
||||||
|
if (acceleratedFeeRate > tx.rate) {
|
||||||
|
tx.rate = acceleratedFeeRate;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tx.acc = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +494,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.oobSubscription = block$.pipe(
|
this.oobSubscription = block$.pipe(
|
||||||
|
filter(() => this.stateService.env.PUBLIC_ACCELERATIONS === true && this.stateService.network === ''),
|
||||||
switchMap((block) => this.apiService.getAccelerationsByHeight$(block.height)
|
switchMap((block) => this.apiService.getAccelerationsByHeight$(block.height)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(accelerations => {
|
map(accelerations => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user