Compare commits
557 Commits
v2.2.1
...
v2.3.0-rc5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
898ff5da23 | ||
|
|
d78d2c0eca | ||
|
|
a08e77ff3e | ||
|
|
1e39eb0fa5 | ||
|
|
5de133ae6a | ||
|
|
d27b125848 | ||
|
|
ad36d53bb5 | ||
|
|
24f76f2f37 | ||
|
|
691bdda523 | ||
|
|
81bb31090e | ||
|
|
cc0a0719b6 | ||
|
|
7dca8ae1a0 | ||
|
|
84027d5568 | ||
|
|
4116186c1a | ||
|
|
358301020f | ||
|
|
642022bfd8 | ||
|
|
70f25b6c9c | ||
|
|
c778e84247 | ||
|
|
4de1d017ad | ||
|
|
61851be23a | ||
|
|
5de949eaed | ||
|
|
de6434a5ba | ||
|
|
c8639ec71d | ||
|
|
e1275c62cc | ||
|
|
be45e88056 | ||
|
|
990ab3da5f | ||
|
|
d1d74ebf37 | ||
|
|
6ab79b3c35 | ||
|
|
4f21fc0d87 | ||
|
|
10c4e47091 | ||
|
|
dd49ff0084 | ||
|
|
853314ba58 | ||
|
|
784e2470df | ||
|
|
350b4922da | ||
|
|
40fb1792f4 | ||
|
|
7ce1cc5103 | ||
|
|
71a4e24900 | ||
|
|
a48c2c07b0 | ||
|
|
d89d7efbe6 | ||
|
|
5ea4b043d9 | ||
|
|
dd4710b602 | ||
|
|
832c0cb3cc | ||
|
|
04216e952a | ||
|
|
951d0f0039 | ||
|
|
706f4bbc55 | ||
|
|
3fd96e412b | ||
|
|
766803ded1 | ||
|
|
504f46cad9 | ||
|
|
fd34761a93 | ||
|
|
96e8f45e5b | ||
|
|
195fae670b | ||
|
|
dd767f9468 | ||
|
|
bc8104eeb4 | ||
|
|
2c61eb6227 | ||
|
|
5d360d4156 | ||
|
|
91e30fbc3c | ||
|
|
5b22e2a000 | ||
|
|
533653e54a | ||
|
|
3dc0dc13ad | ||
|
|
e332789afc | ||
|
|
e4a9fd06b4 | ||
|
|
5845f2380e | ||
|
|
c29311d831 | ||
|
|
252db109bc | ||
|
|
b1c9334119 | ||
|
|
ab04247726 | ||
|
|
e94a85b989 | ||
|
|
a4569788f8 | ||
|
|
b455814e90 | ||
|
|
7afd0f3fe7 | ||
|
|
a2a85469cf | ||
|
|
94488a6029 | ||
|
|
8e4829146a | ||
|
|
08f185525c | ||
|
|
d6b00fe39e | ||
|
|
cec3baeaa4 | ||
|
|
6e59733cac | ||
|
|
c5b705ede7 | ||
|
|
2819e24efe | ||
|
|
5f9bc4497a | ||
|
|
086b14e816 | ||
|
|
958bfe6d25 | ||
|
|
e01ab449cf | ||
|
|
eeb0f403a3 | ||
|
|
9a18019d9d | ||
|
|
bcbc60b456 | ||
|
|
0d14c30892 | ||
|
|
5d8c970351 | ||
|
|
89fede9e48 | ||
|
|
f8a54784d0 | ||
|
|
010381aac4 | ||
|
|
49c30c7237 | ||
|
|
7ba0055c61 | ||
|
|
72b631a4dc | ||
|
|
1a8fd23b05 | ||
|
|
b9a2143a5a | ||
|
|
3ae46e6ba1 | ||
|
|
815fb62e7d | ||
|
|
7a19da560e | ||
|
|
43a1af4509 | ||
|
|
40f1949654 | ||
|
|
cf1471acca | ||
|
|
a2a69e522c | ||
|
|
4939941c88 | ||
|
|
a643f50016 | ||
|
|
836444a1c5 | ||
|
|
24d4b643e5 | ||
|
|
7f045ae5b9 | ||
|
|
96a2c8ab4e | ||
|
|
47103d85d3 | ||
|
|
0af5d733c4 | ||
|
|
bc7bbf5fe5 | ||
|
|
4bc141cd67 | ||
|
|
1c6b3a46c6 | ||
|
|
b41e32915f | ||
|
|
adb5bfe93f | ||
|
|
f4b7bbc91c | ||
|
|
687b4af272 | ||
|
|
e67f552fbd | ||
|
|
28d3f190ff | ||
|
|
2281116504 | ||
|
|
92d745168c | ||
|
|
cf0af20947 | ||
|
|
aee319ed51 | ||
|
|
dc5ced416d | ||
|
|
c9f5002dc2 | ||
|
|
6e4985602e | ||
|
|
11577842a2 | ||
|
|
1cc8a9469a | ||
|
|
7e7dbdbaf2 | ||
|
|
fb152b6d7b | ||
|
|
9e8a741d97 | ||
|
|
2b0d543ce7 | ||
|
|
5e729373bb | ||
|
|
41f3f0ab46 | ||
|
|
37722fe165 | ||
|
|
cbd187d06f | ||
|
|
833dd3ef9d | ||
|
|
bd19496350 | ||
|
|
ec420d8f3e | ||
|
|
9510c7d2ea | ||
|
|
92d2eb9727 | ||
|
|
72f7284311 | ||
|
|
2b3463519a | ||
|
|
00352d7e36 | ||
|
|
63e799b225 | ||
|
|
3c1e264464 | ||
|
|
ff148f15c4 | ||
|
|
a5c7f152aa | ||
|
|
f0be19409f | ||
|
|
936a306dc4 | ||
|
|
8ff5748368 | ||
|
|
271726bf58 | ||
|
|
65e11c1aa1 | ||
|
|
0ccc992e4b | ||
|
|
52397cb341 | ||
|
|
f7a9b691a8 | ||
|
|
8f8c9c1fbc | ||
|
|
9e8ee4fedc | ||
|
|
30ffc0b56e | ||
|
|
053fb4d1b2 | ||
|
|
53cfda533d | ||
|
|
2d557195de | ||
|
|
ed9b46bae0 | ||
|
|
23c9bf489a | ||
|
|
7c89dde5d4 | ||
|
|
87ca5b85d7 | ||
|
|
a2a43f4647 | ||
|
|
9ac77f7113 | ||
|
|
b6215dd54f | ||
|
|
69d5cfe98e | ||
|
|
73d9cd7ab7 | ||
|
|
84a6d29914 | ||
|
|
4633164f99 | ||
|
|
b22b63c9a2 | ||
|
|
40cd2a1ac3 | ||
|
|
e28f5cc403 | ||
|
|
1594fed853 | ||
|
|
41c61ef506 | ||
|
|
53b0bc0e48 | ||
|
|
e00234dfb9 | ||
|
|
8412e28073 | ||
|
|
10a110e682 | ||
|
|
1e40ec4fd0 | ||
|
|
9998a64fa5 | ||
|
|
d1e3c55a28 | ||
|
|
047220bd32 | ||
|
|
1ce05a3ac9 | ||
|
|
0271584b69 | ||
|
|
01da46daca | ||
|
|
73f558db6e | ||
|
|
feb8e35ec3 | ||
|
|
79e44479e9 | ||
|
|
9a39d3207f | ||
|
|
7e55e836fd | ||
|
|
571bf3fa64 | ||
|
|
0b1cf052a8 | ||
|
|
b301840bb8 | ||
|
|
a0e5a79ec4 | ||
|
|
8da89230c9 | ||
|
|
2408e81537 | ||
|
|
9377d80dbb | ||
|
|
8c200bc0e4 | ||
|
|
35ca8c846b | ||
|
|
4b1acfc77e | ||
|
|
8569523b89 | ||
|
|
9bef7da210 | ||
|
|
6f6b2a4355 | ||
|
|
783530b3de | ||
|
|
36777a8c5b | ||
|
|
638d6d896a | ||
|
|
2afb9d359f | ||
|
|
60a8d0ce6e | ||
|
|
e3aeb784ad | ||
|
|
8124274880 | ||
|
|
836f0f065d | ||
|
|
062fdb3658 | ||
|
|
7cfbd2c70d | ||
|
|
5b9ae2eaf5 | ||
|
|
d534c42c47 | ||
|
|
15a0644bd1 | ||
|
|
6ad4e655ea | ||
|
|
598920d6a9 | ||
|
|
d816e67ce4 | ||
|
|
662aafff57 | ||
|
|
cfec9345c8 | ||
|
|
9cb203f98a | ||
|
|
3bab50a43b | ||
|
|
0639ce9b07 | ||
|
|
34c9ca4197 | ||
|
|
14d015a904 | ||
|
|
38412753fe | ||
|
|
475f9344a0 | ||
|
|
d9cf6a2604 | ||
|
|
9eb159617f | ||
|
|
2dc930fad2 | ||
|
|
c478478f86 | ||
|
|
7a07f67be5 | ||
|
|
a3de75ebf4 | ||
|
|
46a2854f67 | ||
|
|
033f066abf | ||
|
|
97244c7b35 | ||
|
|
3d92369ed6 | ||
|
|
2bee46951c | ||
|
|
61ffcd0065 | ||
|
|
88e79c220f | ||
|
|
a2c21e9036 | ||
|
|
8a0316a562 | ||
|
|
055c1f2aa5 | ||
|
|
88e48df8a9 | ||
|
|
b81d296635 | ||
|
|
9f68f57d8a | ||
|
|
26a540a57c | ||
|
|
ad8398e3d4 | ||
|
|
0fabf8dbc3 | ||
|
|
cd63c6c0c1 | ||
|
|
c95f75254b | ||
|
|
9e4ad51b79 | ||
|
|
6e35102b3a | ||
|
|
d1e72c0cc0 | ||
|
|
1925023eb2 | ||
|
|
377eb0cae5 | ||
|
|
e33e4b1d71 | ||
|
|
b8f3c1f124 | ||
|
|
5333ec0b99 | ||
|
|
f761c5d966 | ||
|
|
3b6d07cace | ||
|
|
cbeeef3b2c | ||
|
|
5dab44e6c4 | ||
|
|
5139ffb4df | ||
|
|
06706be18f | ||
|
|
9264f3cf4f | ||
|
|
d6a9017267 | ||
|
|
bad75cfd4e | ||
|
|
68240e4f5c | ||
|
|
9d9ff6ed91 | ||
|
|
f19b84090a | ||
|
|
85c4574cbd | ||
|
|
e2c4e42c81 | ||
|
|
dd6c26b079 | ||
|
|
837992f7ea | ||
|
|
40914236d4 | ||
|
|
f52c36093e | ||
|
|
a9f4418e1a | ||
|
|
1a37c8b116 | ||
|
|
39d231bb3c | ||
|
|
4046c3176f | ||
|
|
c257fbfdcb | ||
|
|
1659be0d9f | ||
|
|
6f9762d50b | ||
|
|
9bf475bc97 | ||
|
|
e59a318cad | ||
|
|
57b64f64ad | ||
|
|
af3af5f099 | ||
|
|
fec603d5c5 | ||
|
|
ed2ebb1c70 | ||
|
|
14d2f8dd97 | ||
|
|
bf563cc195 | ||
|
|
f66e0a2c12 | ||
|
|
a43cd48795 | ||
|
|
44339daedf | ||
|
|
14b7b6427a | ||
|
|
a2e866d15a | ||
|
|
c2f288a861 | ||
|
|
e1c943d0a7 | ||
|
|
fa2d2e60b5 | ||
|
|
c919980652 | ||
|
|
b48389ae7d | ||
|
|
2bac7f9987 | ||
|
|
acf6fd9db5 | ||
|
|
74a9b65e81 | ||
|
|
822c840e54 | ||
|
|
6e93ef68fe | ||
|
|
3006deae6e | ||
|
|
740f5c2003 | ||
|
|
5c9d44e9eb | ||
|
|
88527b41e7 | ||
|
|
83cce0c3a7 | ||
|
|
e144d0c8e5 | ||
|
|
d72dbc1415 | ||
|
|
b857a7c37f | ||
|
|
c72c287b27 | ||
|
|
18e0a17d26 | ||
|
|
87eeef5d41 | ||
|
|
76a2fdeea7 | ||
|
|
792eb3727c | ||
|
|
2e0845847d | ||
|
|
8f774e55a8 | ||
|
|
28418640bb | ||
|
|
8aae5c1c9c | ||
|
|
92048964d1 | ||
|
|
b2140c2abe | ||
|
|
d0a8509194 | ||
|
|
aa0c3e6fed | ||
|
|
f0462114f3 | ||
|
|
9e0f9840aa | ||
|
|
d763c30f6a | ||
|
|
92b69657da | ||
|
|
d9ec0c1a36 | ||
|
|
4bf9f8b062 | ||
|
|
eefd8104bb | ||
|
|
16e807c4b0 | ||
|
|
461296e002 | ||
|
|
86c877c8e9 | ||
|
|
80fcceef73 | ||
|
|
b0e54818ae | ||
|
|
acbd7f0bde | ||
|
|
9a6efceb34 | ||
|
|
5ce7b55441 | ||
|
|
a3edaf17cc | ||
|
|
5695019216 | ||
|
|
7ab1ce8fc4 | ||
|
|
1f8ec2bd8e | ||
|
|
78b488466e | ||
|
|
66630743f6 | ||
|
|
ffa18bbe71 | ||
|
|
8cb1c5c88c | ||
|
|
bb07031362 | ||
|
|
31a0d44543 | ||
|
|
f90e19c767 | ||
|
|
800625d80e | ||
|
|
552540f510 | ||
|
|
bbee2dcb5b | ||
|
|
e4e338b05a | ||
|
|
061a55b236 | ||
|
|
9f0f9230fb | ||
|
|
40956c0a23 | ||
|
|
f29e35b325 | ||
|
|
d88efb8b0d | ||
|
|
b9489525c6 | ||
|
|
8ddcd298b0 | ||
|
|
69df6e4dcb | ||
|
|
f3c8e2134b | ||
|
|
0e25c52e67 | ||
|
|
60f41d3181 | ||
|
|
50c5244abf | ||
|
|
605c1a980c | ||
|
|
0d67bc36ee | ||
|
|
aa39bbd091 | ||
|
|
641d2ad028 | ||
|
|
d602b20f56 | ||
|
|
138f6e4e39 | ||
|
|
3e788ecbf9 | ||
|
|
2236c6d9a6 | ||
|
|
2a0a1b0213 | ||
|
|
e6e49fd5d6 | ||
|
|
f6d5f44469 | ||
|
|
51e09ff64f | ||
|
|
07ba2f6ecc | ||
|
|
bc42552bec | ||
|
|
c6b44a3be9 | ||
|
|
e1f4de0de3 | ||
|
|
d22dc0888a | ||
|
|
75fb27c690 | ||
|
|
401506a103 | ||
|
|
ab27ea28f0 | ||
|
|
6e579ce0b6 | ||
|
|
e7030cca32 | ||
|
|
2c496e9a50 | ||
|
|
014d6dee66 | ||
|
|
47ae306a75 | ||
|
|
8b8b06e6ab | ||
|
|
fa7a45421e | ||
|
|
d376ba1c61 | ||
|
|
388aa7fbe3 | ||
|
|
34695146ee | ||
|
|
9020c618f0 | ||
|
|
584d091d4e | ||
|
|
f434e50a2c | ||
|
|
1a7decb91d | ||
|
|
3574f8639e | ||
|
|
9b956ff88d | ||
|
|
1a98a14541 | ||
|
|
0088e58c14 | ||
|
|
3fad765269 | ||
|
|
2e122a9be1 | ||
|
|
2978d16148 | ||
|
|
fc58bcb3bc | ||
|
|
1696623e2f | ||
|
|
251a1af442 | ||
|
|
9bdf42530a | ||
|
|
8525fbb177 | ||
|
|
63a3568481 | ||
|
|
e4941740de | ||
|
|
25bd33f7da | ||
|
|
2d007b9100 | ||
|
|
b71330c606 | ||
|
|
844b640c8c | ||
|
|
1277e58e68 | ||
|
|
fde6fe324a | ||
|
|
4ed114a4d5 | ||
|
|
8c2dfea6a6 | ||
|
|
a0624df06b | ||
|
|
1eedcf900b | ||
|
|
0b077d6fda | ||
|
|
80047313e7 | ||
|
|
71229b94c8 | ||
|
|
c256daf8c8 | ||
|
|
ba0fb996d2 | ||
|
|
5977e96034 | ||
|
|
a151c5cddd | ||
|
|
0323fd966d | ||
|
|
beb834bc30 | ||
|
|
ad6503c7b3 | ||
|
|
f8c11c8b6b | ||
|
|
ba5421e77b | ||
|
|
20fa803cee | ||
|
|
393fa78a43 | ||
|
|
3f290dae06 | ||
|
|
24d18b9f2f | ||
|
|
79ef8ca371 | ||
|
|
ec12f21113 | ||
|
|
2e8ecc7277 | ||
|
|
fc28b06a0f | ||
|
|
8fdbfdc04c | ||
|
|
bdfcfc96a8 | ||
|
|
bb8649bc81 | ||
|
|
777e3d58b7 | ||
|
|
c552f1aab6 | ||
|
|
c0f2fa3042 | ||
|
|
05936f82bd | ||
|
|
c7db81c97c | ||
|
|
bd1a37b8ef | ||
|
|
efc4e6a8ed | ||
|
|
dd5d87e91e | ||
|
|
ca6df488c5 | ||
|
|
1e018a6aa5 | ||
|
|
0a627f96be | ||
|
|
17a8e67d8a | ||
|
|
815c2c5ad5 | ||
|
|
4376de85ff | ||
|
|
7e89de4612 | ||
|
|
4b72a14706 | ||
|
|
b34f6fedb6 | ||
|
|
58af0d78af | ||
|
|
ca13d9109c | ||
|
|
e103fb5876 | ||
|
|
58178f4563 | ||
|
|
04f1879fd1 | ||
|
|
f5bc9ced0a | ||
|
|
7fe9993f91 | ||
|
|
7c95339324 | ||
|
|
006442f9de | ||
|
|
e20100e437 | ||
|
|
d7cf2b37d5 | ||
|
|
278c2b9aae | ||
|
|
944246fcf5 | ||
|
|
03d87f4993 | ||
|
|
cf8cab5f77 | ||
|
|
49d1376647 | ||
|
|
de5518d262 | ||
|
|
7e4c51f47f | ||
|
|
fe1d153632 | ||
|
|
a98f9ab80e | ||
|
|
867afaf265 | ||
|
|
3d2ec64b14 | ||
|
|
bb407c0b42 | ||
|
|
83c3d901c7 | ||
|
|
901cee903c | ||
|
|
250ea09c7e | ||
|
|
648d59631b | ||
|
|
ed06e3c491 | ||
|
|
3e8d646edd | ||
|
|
9c2c698575 | ||
|
|
e2b0a286a4 | ||
|
|
154809f0f9 | ||
|
|
8d9a51a7c4 | ||
|
|
b3294369d4 | ||
|
|
53730920e3 | ||
|
|
d73b814277 | ||
|
|
dd0050c066 | ||
|
|
ae51ee3e26 | ||
|
|
4b16e5d65f | ||
|
|
4f73bba132 | ||
|
|
3c229602e4 | ||
|
|
c74c902ebc | ||
|
|
8bfd315ba3 | ||
|
|
9d75c47792 | ||
|
|
e183be1a5c | ||
|
|
7e273ce63d | ||
|
|
6d070e75b0 | ||
|
|
f4f96fd18e | ||
|
|
ddd6420d9b | ||
|
|
d76f42296a | ||
|
|
47a6118ffb | ||
|
|
dbd205b73f | ||
|
|
7ef4be26ed | ||
|
|
c6b1979391 | ||
|
|
0f390e65a4 | ||
|
|
5dc0f4e270 | ||
|
|
223288cc52 | ||
|
|
e1f07884b9 | ||
|
|
e00e61edfa | ||
|
|
4f988e186a | ||
|
|
1aa54faa35 | ||
|
|
0bb9247609 | ||
|
|
d841933b21 | ||
|
|
bc8b78a01b | ||
|
|
c6e72be483 | ||
|
|
ef7dd6c8fb | ||
|
|
e6b90385b2 | ||
|
|
61181c6791 | ||
|
|
d2cccd2422 | ||
|
|
b05ebe1598 | ||
|
|
d061f7589c | ||
|
|
15903faf49 | ||
|
|
1908b1a5a6 | ||
|
|
037f472f8c | ||
|
|
a32c1f40b1 | ||
|
|
a00aa27ae4 | ||
|
|
544be77bdc | ||
|
|
b8a110a772 | ||
|
|
7788a2d6bd | ||
|
|
da17fd16fa | ||
|
|
e670f80fed | ||
|
|
2de28b9926 | ||
|
|
d7586af392 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://mempool.space/about'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
custom: ['https://mempool.space/sponsor'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Report bugs or other issues to us on GitHub
|
||||
---
|
||||
|
||||
<!--
|
||||
SUPPORT REQUESTS: This is for reporting bugs in Mempool.
|
||||
If you have a support request, please join our Keybase group:
|
||||
SUPPORT REQUESTS:
|
||||
This is for reporting bugs in Mempool, not for support requests.
|
||||
If you have a support request, please join our Keybase or Matrix:
|
||||
https://keybase.io/team/mempool
|
||||
https://matrix.to/#/#mempool:bitcoin.kyoto
|
||||
-->
|
||||
|
||||
### Description
|
||||
@@ -14,11 +21,11 @@
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--if you can reliably reproduce the bug, list the steps here -->
|
||||
<!-- if you can reliably reproduce the bug, list the steps here -->
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
<!--description of the expected behavior -->
|
||||
<!-- description of the expected behavior -->
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
@@ -26,7 +33,7 @@
|
||||
|
||||
### Screenshots
|
||||
|
||||
<!--Screenshots if gui related, drag and drop to add to the issue -->
|
||||
<!-- Screenshots if gui related, drag and drop to add to the issue -->
|
||||
|
||||
#### Device or machine
|
||||
|
||||
28
.github/ISSUE_TEMPLATE/30-feature-request.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/30-feature-request.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: ✨ Feature Request
|
||||
about: Request a feature or suggest other enhancements 💡
|
||||
---
|
||||
|
||||
<!--
|
||||
SUPPORT REQUESTS:
|
||||
This is for requesting features in Mempool, not for support requests.
|
||||
If you have a support request, please join our Keybase or Matrix:
|
||||
https://keybase.io/team/mempool
|
||||
https://matrix.to/#/#mempool:bitcoin.kyoto
|
||||
-->
|
||||
|
||||
### Description
|
||||
|
||||
<!-- brief description of the feature request -->
|
||||
|
||||
### Problem to be solved
|
||||
|
||||
<!-- description of the the problem you're having -->
|
||||
|
||||
### Proposed solution
|
||||
|
||||
<!-- explain how you think we should solve the problem -->
|
||||
|
||||
#### Additional info
|
||||
|
||||
<!-- Additional information useful for implementing (e.g. docs, links, etc.) -->
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 Need help? Chat with us on Matrix
|
||||
url: https://matrix.to/#/#mempool:bitcoin.kyoto
|
||||
about: For support requests or general questions
|
||||
- name: 💬 Need help? Chat with us on Keybase
|
||||
url: https://keybase.io/team/mempool
|
||||
about: For support requests or general questions
|
||||
52
.github/workflows/cypress.yml
vendored
52
.github/workflows/cypress.yml
vendored
@@ -15,17 +15,63 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: ${{ matrix.browser }} browser tests
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.10.0
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
- name: ${{ matrix.browser }} browser tests (Mempool)
|
||||
uses: cypress-io/github-action@v2
|
||||
with:
|
||||
working-directory: frontend
|
||||
build: npm run config:defaults
|
||||
build: npm run config:defaults:mempool
|
||||
start: npm run start:local-prod
|
||||
wait-on: 'http://localhost:4200'
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
group: Tests on ${{ matrix.browser }}
|
||||
group: Tests on ${{ matrix.browser }} (Mempool)
|
||||
browser: ${{ matrix.browser }}
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||
|
||||
- name: ${{ matrix.browser }} browser tests (Liquid)
|
||||
uses: cypress-io/github-action@v2
|
||||
if: always()
|
||||
with:
|
||||
working-directory: frontend
|
||||
build: npm run config:defaults:liquid
|
||||
start: npm run start:local-prod
|
||||
wait-on: 'http://localhost:4200'
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: cypress/integration/liquid/liquid.spec.ts
|
||||
group: Tests on ${{ matrix.browser }} (Liquid)
|
||||
browser: ${{ matrix.browser }}
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||
|
||||
- name: ${{ matrix.browser }} browser tests (Bisq)
|
||||
uses: cypress-io/github-action@v2
|
||||
if: always()
|
||||
with:
|
||||
working-directory: frontend
|
||||
build: npm run config:defaults:bisq
|
||||
start: npm run start:local-prod
|
||||
wait-on: 'http://localhost:4200'
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: cypress/integration/bisq/bisq.spec.ts
|
||||
group: Tests on ${{ matrix.browser }} (Bisq)
|
||||
browser: ${{ matrix.browser }}
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
env:
|
||||
|
||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"typescript.tsdk": "./backend/node_modules/typescript/lib"
|
||||
}
|
||||
@@ -25,8 +25,7 @@ help:
|
||||
.PHONY: init
|
||||
init:
|
||||
@echo ''
|
||||
mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/db-scripts $(DATA)/mysql/data
|
||||
install -v mariadb-structure.sql $(DATA)/mysql/db-scripts
|
||||
mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/data
|
||||
#REF: https://github.com/mempool/mempool/blob/master/docker/README.md
|
||||
cat docker/docker-compose.yml > docker-compose.yml
|
||||
cat backend/mempool-config.sample.json > backend/mempool-config.json
|
||||
|
||||
45
README.md
45
README.md
@@ -1,8 +1,8 @@
|
||||
# The Mempool Open Source Project
|
||||
# The Mempool Open Source Project™
|
||||
|
||||
Mempool is the fully featured visualizer, explorer, and API service running on [mempool.space](https://mempool.space/), an open source project developed and operated for the benefit of the Bitcoin community, with a focus on the emerging transaction fee market to help our transition into a multi-layer ecosystem.
|
||||
|
||||

|
||||

|
||||
|
||||
## Installation Methods
|
||||
|
||||
@@ -20,11 +20,11 @@ The following instructions are for a manual installation on Linux or FreeBSD. Th
|
||||
|
||||
## Dependencies
|
||||
|
||||
* Bitcoin Core (no pruning, txindex=1)
|
||||
* Electrum Server (romanz/electrs)
|
||||
* NodeJS (official stable LTS)
|
||||
* MariaDB (default config)
|
||||
* Nginx (use supplied nginx.conf and nginx-mempool.conf)
|
||||
* [Bitcoin](https://github.com/bitcoin/bitcoin)
|
||||
* [Electrum](https://github.com/romanz/electrs)
|
||||
* [NodeJS](https://github.com/nodejs/node)
|
||||
* [MariaDB](https://github.com/mariadb/server)
|
||||
* [Nginx](https://github.com/nginx/nginx)
|
||||
|
||||
## Mempool
|
||||
|
||||
@@ -41,7 +41,7 @@ Clone the mempool repo, and checkout the latest release tag:
|
||||
Enable RPC and txindex in `bitcoin.conf`:
|
||||
```bash
|
||||
rpcuser=mempool
|
||||
rpcpassword=71b61986da5b03a5694d7c7d5165ece5
|
||||
rpcpassword=mempool
|
||||
txindex=1
|
||||
```
|
||||
|
||||
@@ -54,7 +54,7 @@ Install MariaDB from OS package manager:
|
||||
|
||||
# macOS
|
||||
brew install mariadb
|
||||
brew services start mariadb
|
||||
mysql.server start
|
||||
```
|
||||
|
||||
Create database and grant privileges:
|
||||
@@ -69,18 +69,13 @@ Create database and grant privileges:
|
||||
Query OK, 0 rows affected (0.00 sec)
|
||||
```
|
||||
|
||||
From the mempool repo's top-level folder, import the database structure:
|
||||
```bash
|
||||
mysql -u mempool -p mempool < mariadb-structure.sql
|
||||
```
|
||||
|
||||
## Mempool Backend
|
||||
Install mempool dependencies from npm and build the backend:
|
||||
|
||||
```bash
|
||||
# backend
|
||||
cd backend
|
||||
npm install
|
||||
npm install --prod
|
||||
npm run build
|
||||
```
|
||||
|
||||
@@ -96,18 +91,18 @@ Edit `mempool-config.json` to add your Bitcoin Core node RPC credentials:
|
||||
"MEMPOOL": {
|
||||
"NETWORK": "mainnet",
|
||||
"BACKEND": "electrum",
|
||||
"HTTP_PORT": 8999,
|
||||
"API_URL_PREFIX": "/api/v1/",
|
||||
"POLL_RATE_MS": 2000
|
||||
"HTTP_PORT": 8999
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 8332,
|
||||
"USERNAME": "mempool",
|
||||
"PASSWORD": "71b61986da5b03a5694d7c7d5165ece5"
|
||||
"PASSWORD": "mempool"
|
||||
},
|
||||
"ELECTRUM": {
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 50002,
|
||||
"TLS_ENABLED": true,
|
||||
"TLS_ENABLED": true
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
@@ -116,10 +111,6 @@ Edit `mempool-config.json` to add your Bitcoin Core node RPC credentials:
|
||||
"USERNAME": "mempool",
|
||||
"PASSWORD": "mempool",
|
||||
"DATABASE": "mempool"
|
||||
},
|
||||
"STATISTICS": {
|
||||
"ENABLED": true,
|
||||
"TX_PER_SECOND_SAMPLE_PERIOD": 150
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -160,14 +151,14 @@ Install mempool dependencies from npm and build the frontend static HTML/CSS/JS:
|
||||
```bash
|
||||
# frontend
|
||||
cd frontend
|
||||
npm install
|
||||
npm install --prod
|
||||
npm run build
|
||||
```
|
||||
|
||||
Install the output into nginx webroot folder:
|
||||
|
||||
```bash
|
||||
sudo rsync -av --delete dist/mempool /var/www/
|
||||
sudo rsync -av --delete dist/mempool/ /var/www/
|
||||
```
|
||||
|
||||
## nginx + certbot
|
||||
@@ -176,7 +167,7 @@ Install the supplied nginx.conf and nginx-mempool.conf in /etc/nginx
|
||||
|
||||
```bash
|
||||
# install nginx and certbot
|
||||
apt-get install -y nginx python-certbot-nginx
|
||||
apt-get install -y nginx python3-certbot-nginx
|
||||
|
||||
# install the mempool configuration for nginx
|
||||
cp nginx.conf nginx-mempool.conf /etc/nginx/
|
||||
|
||||
7
backend/.gitignore
vendored
7
backend/.gitignore
vendored
@@ -1,7 +1,10 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# production config
|
||||
mempool-config.json
|
||||
# production config and external assets
|
||||
*.json
|
||||
!mempool-config.sample.json
|
||||
|
||||
icons.json
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
|
||||
4
backend/.vscode/settings.json
vendored
Normal file
4
backend/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"typescript.tsdk": "../backend/node_modules/typescript/lib"
|
||||
}
|
||||
@@ -8,7 +8,13 @@
|
||||
"POLL_RATE_MS": 2000,
|
||||
"CACHE_DIR": "./cache",
|
||||
"CLEAR_PROTECTION_MINUTES": 20,
|
||||
"RECOMMENDED_FEE_PERCENTILE": 50
|
||||
"RECOMMENDED_FEE_PERCENTILE": 50,
|
||||
"BLOCK_WEIGHT_UNITS": 4000000,
|
||||
"INITIAL_BLOCKS_AMOUNT": 8,
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||
"PRICE_FEED_UPDATE_INTERVAL": 3600,
|
||||
"USE_SECOND_NODE_FOR_MINFEE": false,
|
||||
"EXTERNAL_ASSETS": []
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
@@ -24,8 +30,7 @@
|
||||
"ESPLORA": {
|
||||
"REST_API_URL": "http://127.0.0.1:3000"
|
||||
},
|
||||
"CORE_RPC_MINFEE": {
|
||||
"ENABLED": false,
|
||||
"SECOND_CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 8332,
|
||||
"USERNAME": "mempool",
|
||||
|
||||
523
backend/package-lock.json
generated
523
backend/package-lock.json
generated
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"name": "mempool-backend",
|
||||
"version": "2.2.1",
|
||||
"version": "2.3.0-dev",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mempool-backend",
|
||||
"version": "2.2.1",
|
||||
"version": "2.3.0-dev",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"dependencies": {
|
||||
"@mempool/bitcoin": "^3.0.3",
|
||||
"@mempool/electrum-client": "^1.1.7",
|
||||
"axios": "^0.21.1",
|
||||
"bitcoinjs-lib": "^5.2.0",
|
||||
"@types/ws": "8.2.2",
|
||||
"axios": "0.24.0",
|
||||
"bitcoinjs-lib": "6.0.1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"express": "^4.17.1",
|
||||
"locutus": "^2.0.12",
|
||||
"mysql2": "2.2.5",
|
||||
"mysql2": "2.3.3",
|
||||
"node-worker-threads-pool": "^1.4.3",
|
||||
"ws": "^7.4.6"
|
||||
"typescript": "4.4.4",
|
||||
"ws": "8.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/compression": "^1.0.1",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/locutus": "^0.0.6",
|
||||
"@types/ws": "^7.4.4",
|
||||
"tslint": "^6.1.0",
|
||||
"typescript": "^4.1.5"
|
||||
"tslint": "^6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -137,8 +137,7 @@
|
||||
"node_modules/@types/node": {
|
||||
"version": "14.14.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz",
|
||||
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A=="
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.5",
|
||||
@@ -163,11 +162,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz",
|
||||
"integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
|
||||
"integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -211,11 +208,11 @@
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
@@ -225,25 +222,17 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base-x": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
|
||||
"integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
|
||||
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bech32": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
|
||||
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
|
||||
},
|
||||
"node_modules/bip174": {
|
||||
"version": "2.0.1",
|
||||
@@ -253,71 +242,23 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bip32": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
|
||||
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
|
||||
"dependencies": {
|
||||
"@types/node": "10.12.18",
|
||||
"bs58check": "^2.1.1",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"tiny-secp256k1": "^1.1.3",
|
||||
"typeforce": "^1.11.5",
|
||||
"wif": "^2.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bip32/node_modules/@types/node": {
|
||||
"version": "10.12.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
|
||||
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
|
||||
},
|
||||
"node_modules/bip66": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
|
||||
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoin-ops": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
|
||||
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
|
||||
},
|
||||
"node_modules/bitcoinjs-lib": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz",
|
||||
"integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz",
|
||||
"integrity": "sha512-x/7D4jDj/MMkmO6t3p2CSDXTqpwZ/jRsRiJDmaiXabrR9XRo7jwby8HRn7EyK1h24rKFFI7vI0ay4czl6bDOZQ==",
|
||||
"dependencies": {
|
||||
"bech32": "^1.1.2",
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^2.0.1",
|
||||
"bip32": "^2.0.4",
|
||||
"bip66": "^1.1.0",
|
||||
"bitcoin-ops": "^1.4.0",
|
||||
"bs58check": "^2.0.0",
|
||||
"bs58check": "^2.1.2",
|
||||
"create-hash": "^1.1.0",
|
||||
"create-hmac": "^1.1.3",
|
||||
"merkle-lib": "^2.0.10",
|
||||
"pushdata-bitcoin": "^1.0.1",
|
||||
"randombytes": "^2.0.1",
|
||||
"tiny-secp256k1": "^1.1.1",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.0.4",
|
||||
"varuint-bitcoin": "^1.1.2",
|
||||
"wif": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
@@ -348,11 +289,6 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"node_modules/bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
||||
@@ -487,19 +423,6 @@
|
||||
"sha.js": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/create-hmac": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.3",
|
||||
"create-hash": "^1.1.0",
|
||||
"inherits": "^2.0.1",
|
||||
"ripemd160": "^2.0.0",
|
||||
"safe-buffer": "^5.0.1",
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
|
||||
@@ -514,9 +437,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
|
||||
"integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
|
||||
"integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
@@ -548,20 +471,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"dependencies": {
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
@@ -650,11 +559,6 @@
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
@@ -673,9 +577,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==",
|
||||
"version": "1.14.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz",
|
||||
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -781,25 +685,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"dependencies": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
@@ -937,11 +822,6 @@
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"node_modules/merkle-lib": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz",
|
||||
"integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY="
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
@@ -980,16 +860,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"node_modules/minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@@ -1026,13 +896,13 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz",
|
||||
"integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz",
|
||||
"integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==",
|
||||
"dependencies": {
|
||||
"denque": "^1.4.1",
|
||||
"denque": "^2.0.1",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"long": "^4.0.0",
|
||||
"lru-cache": "^6.0.0",
|
||||
"named-placeholders": "^1.1.2",
|
||||
@@ -1044,9 +914,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2/node_modules/iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
@@ -1079,11 +949,6 @@
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
@@ -1135,9 +1000,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
@@ -1162,14 +1027,6 @@
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
||||
},
|
||||
"node_modules/pushdata-bitcoin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
|
||||
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
|
||||
"dependencies": {
|
||||
"bitcoin-ops": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
@@ -1178,14 +1035,6 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@@ -1382,22 +1231,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-secp256k1": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
|
||||
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.3.0",
|
||||
"bn.js": "^4.11.8",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.4.0",
|
||||
"nan": "^2.13.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
@@ -1473,10 +1306,9 @@
|
||||
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
|
||||
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
|
||||
"dev": true,
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
|
||||
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -1537,11 +1369,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
|
||||
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
@@ -1665,8 +1497,7 @@
|
||||
"@types/node": {
|
||||
"version": "14.14.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz",
|
||||
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A=="
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.5",
|
||||
@@ -1691,10 +1522,9 @@
|
||||
}
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz",
|
||||
"integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==",
|
||||
"dev": true,
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
|
||||
"integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -1732,11 +1562,11 @@
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
@@ -1746,92 +1576,37 @@
|
||||
"dev": true
|
||||
},
|
||||
"base-x": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
|
||||
"integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
|
||||
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"bech32": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
|
||||
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
|
||||
},
|
||||
"bip174": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz",
|
||||
"integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
|
||||
},
|
||||
"bip32": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
|
||||
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
|
||||
"requires": {
|
||||
"@types/node": "10.12.18",
|
||||
"bs58check": "^2.1.1",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"tiny-secp256k1": "^1.1.3",
|
||||
"typeforce": "^1.11.5",
|
||||
"wif": "^2.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "10.12.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
|
||||
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"bip66": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
|
||||
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"bitcoin-ops": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
|
||||
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
|
||||
},
|
||||
"bitcoinjs-lib": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz",
|
||||
"integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz",
|
||||
"integrity": "sha512-x/7D4jDj/MMkmO6t3p2CSDXTqpwZ/jRsRiJDmaiXabrR9XRo7jwby8HRn7EyK1h24rKFFI7vI0ay4czl6bDOZQ==",
|
||||
"requires": {
|
||||
"bech32": "^1.1.2",
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^2.0.1",
|
||||
"bip32": "^2.0.4",
|
||||
"bip66": "^1.1.0",
|
||||
"bitcoin-ops": "^1.4.0",
|
||||
"bs58check": "^2.0.0",
|
||||
"bs58check": "^2.1.2",
|
||||
"create-hash": "^1.1.0",
|
||||
"create-hmac": "^1.1.3",
|
||||
"merkle-lib": "^2.0.10",
|
||||
"pushdata-bitcoin": "^1.0.1",
|
||||
"randombytes": "^2.0.1",
|
||||
"tiny-secp256k1": "^1.1.1",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.0.4",
|
||||
"varuint-bitcoin": "^1.1.2",
|
||||
"wif": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
@@ -1859,11 +1634,6 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
},
|
||||
"bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
||||
@@ -1982,19 +1752,6 @@
|
||||
"sha.js": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"create-hmac": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"requires": {
|
||||
"cipher-base": "^1.0.3",
|
||||
"create-hash": "^1.1.0",
|
||||
"inherits": "^2.0.1",
|
||||
"ripemd160": "^2.0.0",
|
||||
"safe-buffer": "^5.0.1",
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
|
||||
@@ -2009,9 +1766,9 @@
|
||||
}
|
||||
},
|
||||
"denque": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
|
||||
"integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
|
||||
"integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
@@ -2034,20 +1791,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"requires": {
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
@@ -2119,11 +1862,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
@@ -2139,9 +1877,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
|
||||
"version": "1.14.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz",
|
||||
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A=="
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
@@ -2212,25 +1950,6 @@
|
||||
"safe-buffer": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"requires": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
@@ -2346,11 +2065,6 @@
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"merkle-lib": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz",
|
||||
"integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
@@ -2374,16 +2088,6 @@
|
||||
"mime-db": "1.45.0"
|
||||
}
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@@ -2414,13 +2118,13 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"mysql2": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz",
|
||||
"integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz",
|
||||
"integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==",
|
||||
"requires": {
|
||||
"denque": "^1.4.1",
|
||||
"denque": "^2.0.1",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"long": "^4.0.0",
|
||||
"lru-cache": "^6.0.0",
|
||||
"named-placeholders": "^1.1.2",
|
||||
@@ -2429,9 +2133,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
@@ -2462,11 +2166,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
@@ -2506,9 +2205,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
@@ -2530,27 +2229,11 @@
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
||||
},
|
||||
"pushdata-bitcoin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
|
||||
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
|
||||
"requires": {
|
||||
"bitcoin-ops": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@@ -2702,18 +2385,6 @@
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tiny-secp256k1": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
|
||||
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
|
||||
"requires": {
|
||||
"bindings": "^1.3.0",
|
||||
"bn.js": "^4.11.8",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.4.0",
|
||||
"nan": "^2.13.2"
|
||||
}
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
@@ -2770,10 +2441,9 @@
|
||||
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
|
||||
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
|
||||
"dev": true
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
|
||||
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA=="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
@@ -2818,9 +2488,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
|
||||
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
|
||||
"requires": {}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mempool-backend",
|
||||
"version": "2.2.1",
|
||||
"version": "2.3.0-dev",
|
||||
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"homepage": "https://mempool.space",
|
||||
@@ -30,21 +30,21 @@
|
||||
"dependencies": {
|
||||
"@mempool/bitcoin": "^3.0.3",
|
||||
"@mempool/electrum-client": "^1.1.7",
|
||||
"axios": "^0.21.1",
|
||||
"bitcoinjs-lib": "^5.2.0",
|
||||
"@types/ws": "8.2.2",
|
||||
"axios": "0.24.0",
|
||||
"bitcoinjs-lib": "6.0.1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"express": "^4.17.1",
|
||||
"locutus": "^2.0.12",
|
||||
"mysql2": "2.2.5",
|
||||
"mysql2": "2.3.3",
|
||||
"node-worker-threads-pool": "^1.4.3",
|
||||
"ws": "^7.4.6"
|
||||
"typescript": "4.4.4",
|
||||
"ws": "8.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/compression": "^1.0.1",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/locutus": "^0.0.6",
|
||||
"@types/ws": "^7.4.4",
|
||||
"tslint": "^6.1.0",
|
||||
"typescript": "^4.1.5"
|
||||
"tslint": "^6.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class BackendInfo {
|
||||
try {
|
||||
this.gitCommitHash = fs.readFileSync('../.git/refs/heads/master').toString().trim();
|
||||
} catch (e) {
|
||||
logger.err('Could not load git commit info: ' + e.message || e);
|
||||
logger.err('Could not load git commit info: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class BackendInfo {
|
||||
const packageJson = fs.readFileSync('package.json').toString();
|
||||
this.version = JSON.parse(packageJson).version;
|
||||
} catch (e) {
|
||||
throw new Error(e);
|
||||
throw new Error(e instanceof Error ? e.message : 'Error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ class Bisq {
|
||||
this.buildIndex();
|
||||
this.calculateStats();
|
||||
} catch (e) {
|
||||
logger.info('loadBisqDumpFile() error.' + e.message || e);
|
||||
logger.info('loadBisqDumpFile() error.' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ interface BisqScriptPubKey {
|
||||
addresses: string[];
|
||||
asm: string;
|
||||
hex: string;
|
||||
reqSigs: number;
|
||||
reqSigs?: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class Bisq {
|
||||
logger.debug('Bisq market data updated in ' + time + ' ms');
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('loadBisqMarketDataDumpFile() error.' + e.message || e);
|
||||
logger.err('loadBisqMarketDataDumpFile() error.' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,4 +11,12 @@ export interface AbstractBitcoinApi {
|
||||
$getAddress(address: string): Promise<IEsploraApi.Address>;
|
||||
$getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
|
||||
$getAddressPrefix(prefix: string): string[];
|
||||
$sendRawTransaction(rawTransaction: string): Promise<string>;
|
||||
}
|
||||
export interface BitcoinRpcCredentials {
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
pass: string;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
@@ -3,16 +3,17 @@ import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import EsploraApi from './esplora-api';
|
||||
import BitcoinApi from './bitcoin-api';
|
||||
import ElectrumApi from './electrum-api';
|
||||
import bitcoinClient from './bitcoin-client';
|
||||
|
||||
function bitcoinApiFactory(): AbstractBitcoinApi {
|
||||
switch (config.MEMPOOL.BACKEND) {
|
||||
case 'esplora':
|
||||
return new EsploraApi();
|
||||
case 'electrum':
|
||||
return new ElectrumApi();
|
||||
return new ElectrumApi(bitcoinClient);
|
||||
case 'none':
|
||||
default:
|
||||
return new BitcoinApi();
|
||||
return new BitcoinApi(bitcoinClient);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ export namespace IBitcoinApi {
|
||||
time: number; // (numeric) Same as blocktime
|
||||
}
|
||||
|
||||
interface Vin {
|
||||
export interface Vin {
|
||||
txid?: string; // (string) The transaction id
|
||||
vout?: number; // (string)
|
||||
scriptSig?: { // (json object) The script
|
||||
@@ -82,28 +82,36 @@ export namespace IBitcoinApi {
|
||||
sequence: number; // (numeric) The script sequence number
|
||||
txinwitness?: string[]; // (string) hex-encoded witness data
|
||||
coinbase?: string;
|
||||
is_pegin?: boolean; // (boolean) Elements peg-in
|
||||
}
|
||||
|
||||
interface Vout {
|
||||
export interface Vout {
|
||||
value: number; // (numeric) The value in BTC
|
||||
n: number; // (numeric) index
|
||||
asset?: string; // (string) Elements asset id
|
||||
scriptPubKey: { // (json object)
|
||||
asm: string; // (string) the asm
|
||||
hex: string; // (string) the hex
|
||||
reqSigs: number; // (numeric) The required sigs
|
||||
reqSigs?: number; // (numeric) The required sigs
|
||||
type: string; // (string) The type, eg 'pubkeyhash'
|
||||
addresses: string[] // (string) bitcoin address
|
||||
address?: string; // (string) bitcoin address
|
||||
addresses?: string[]; // (string) bitcoin addresses
|
||||
pegout_chain?: string; // (string) Elements peg-out chain
|
||||
pegout_addresses?: string[]; // (string) Elements peg-out addresses
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddressInformation {
|
||||
isvalid: boolean; // (boolean) If the address is valid or not. If not, this is the only property returned.
|
||||
isvalid_parent?: boolean; // (boolean) Elements only
|
||||
address: string; // (string) The bitcoin address validated
|
||||
scriptPubKey: string; // (string) The hex-encoded scriptPubKey generated by the address
|
||||
isscript: boolean; // (boolean) If the key is a script
|
||||
iswitness: boolean; // (boolean) If the address is a witness
|
||||
witness_version?: boolean; // (numeric, optional) The version number of the witness program
|
||||
witness_version?: number; // (numeric, optional) The version number of the witness program
|
||||
witness_program: string; // (string, optional) The hex value of the witness program
|
||||
confidential_key?: string; // (string) Elements only
|
||||
unconfidential?: string; // (string) Elements only
|
||||
}
|
||||
|
||||
export interface ChainTips {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import config from '../../config';
|
||||
import * as bitcoin from '@mempool/bitcoin';
|
||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
||||
@@ -10,16 +8,10 @@ import { TransactionExtended } from '../../mempool.interfaces';
|
||||
|
||||
class BitcoinApi implements AbstractBitcoinApi {
|
||||
private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
|
||||
private bitcoindClient: any;
|
||||
protected bitcoindClient: any;
|
||||
|
||||
constructor() {
|
||||
this.bitcoindClient = new bitcoin.Client({
|
||||
host: config.CORE_RPC.HOST,
|
||||
port: config.CORE_RPC.PORT,
|
||||
user: config.CORE_RPC.USERNAME,
|
||||
pass: config.CORE_RPC.PASSWORD,
|
||||
timeout: 60000,
|
||||
});
|
||||
constructor(bitcoinClient: any) {
|
||||
this.bitcoindClient = bitcoinClient;
|
||||
}
|
||||
|
||||
$getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
|
||||
@@ -60,13 +52,12 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
return this.bitcoindClient.getBlock(hash, 0);
|
||||
}
|
||||
|
||||
|
||||
$getBlockHash(height: number): Promise<string> {
|
||||
return this.bitcoindClient.getBlockHash(height);
|
||||
}
|
||||
|
||||
$getBlockHeader(hash: string): Promise<string> {
|
||||
return this.bitcoindClient.getBlockHeader(hash,false);
|
||||
return this.bitcoindClient.getBlockHeader(hash, false);
|
||||
}
|
||||
|
||||
async $getBlock(hash: string): Promise<IEsploraApi.Block> {
|
||||
@@ -107,6 +98,10 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
return found;
|
||||
}
|
||||
|
||||
$sendRawTransaction(rawTransaction: string): Promise<string> {
|
||||
return this.bitcoindClient.sendRawTransaction(rawTransaction);
|
||||
}
|
||||
|
||||
protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean): Promise<IEsploraApi.Transaction> {
|
||||
let esploraTransaction: IEsploraApi.Transaction = {
|
||||
txid: transaction.txid,
|
||||
@@ -124,7 +119,8 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
return {
|
||||
value: vout.value * 100000000,
|
||||
scriptpubkey: vout.scriptPubKey.hex,
|
||||
scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : '',
|
||||
scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.address ? vout.scriptPubKey.address
|
||||
: vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : '',
|
||||
scriptpubkey_asm: vout.scriptPubKey.asm ? this.convertScriptSigAsm(vout.scriptPubKey.asm) : '',
|
||||
scriptpubkey_type: this.translateScriptPubKeyType(vout.scriptPubKey.type),
|
||||
};
|
||||
@@ -238,10 +234,6 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
});
|
||||
}
|
||||
|
||||
protected $validateAddress(address: string): Promise<IBitcoinApi.AddressInformation> {
|
||||
return this.bitcoindClient.validateAddress(address);
|
||||
}
|
||||
|
||||
private $getMempoolEntry(txid: string): Promise<IBitcoinApi.MempoolEntry> {
|
||||
return this.bitcoindClient.getMempoolEntry(txid);
|
||||
}
|
||||
@@ -307,6 +299,11 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
const witnessScript = vin.witness[vin.witness.length - 1];
|
||||
vin.inner_witnessscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(witnessScript, 'hex')));
|
||||
}
|
||||
|
||||
if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness && vin.witness.length > 1) {
|
||||
const witnessScript = vin.witness[vin.witness.length - 2];
|
||||
vin.inner_witnessscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(witnessScript, 'hex')));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import config from '../../config';
|
||||
import * as bitcoin from '@mempool/bitcoin';
|
||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
||||
|
||||
class BitcoinBaseApi {
|
||||
bitcoindClient: any;
|
||||
bitcoindClientMempoolInfo: any;
|
||||
|
||||
constructor() {
|
||||
this.bitcoindClient = new bitcoin.Client({
|
||||
host: config.CORE_RPC.HOST,
|
||||
port: config.CORE_RPC.PORT,
|
||||
user: config.CORE_RPC.USERNAME,
|
||||
pass: config.CORE_RPC.PASSWORD,
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
if (config.CORE_RPC_MINFEE.ENABLED) {
|
||||
this.bitcoindClientMempoolInfo = new bitcoin.Client({
|
||||
host: config.CORE_RPC_MINFEE.HOST,
|
||||
port: config.CORE_RPC_MINFEE.PORT,
|
||||
user: config.CORE_RPC_MINFEE.USERNAME,
|
||||
pass: config.CORE_RPC_MINFEE.PASSWORD,
|
||||
timeout: 60000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$getMempoolInfo(): Promise<IBitcoinApi.MempoolInfo> {
|
||||
if (config.CORE_RPC_MINFEE.ENABLED) {
|
||||
return Promise.all([
|
||||
this.bitcoindClient.getMempoolInfo(),
|
||||
this.bitcoindClientMempoolInfo.getMempoolInfo()
|
||||
]).then(([mempoolInfo, secondMempoolInfo]) => {
|
||||
mempoolInfo.maxmempool = secondMempoolInfo.maxmempool;
|
||||
mempoolInfo.mempoolminfee = secondMempoolInfo.mempoolminfee;
|
||||
mempoolInfo.minrelaytxfee = secondMempoolInfo.minrelaytxfee;
|
||||
return mempoolInfo;
|
||||
});
|
||||
}
|
||||
return this.bitcoindClient.getMempoolInfo();
|
||||
}
|
||||
|
||||
$getBlockchainInfo(): Promise<IBitcoinApi.BlockchainInfo> {
|
||||
return this.bitcoindClient.getBlockchainInfo();
|
||||
}
|
||||
}
|
||||
|
||||
export default new BitcoinBaseApi();
|
||||
13
backend/src/api/bitcoin/bitcoin-client.ts
Normal file
13
backend/src/api/bitcoin/bitcoin-client.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import config from '../../config';
|
||||
import * as bitcoin from '@mempool/bitcoin';
|
||||
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
|
||||
|
||||
const nodeRpcCredentials: BitcoinRpcCredentials = {
|
||||
host: config.CORE_RPC.HOST,
|
||||
port: config.CORE_RPC.PORT,
|
||||
user: config.CORE_RPC.USERNAME,
|
||||
pass: config.CORE_RPC.PASSWORD,
|
||||
timeout: 60000,
|
||||
};
|
||||
|
||||
export default new bitcoin.Client(nodeRpcCredentials);
|
||||
13
backend/src/api/bitcoin/bitcoin-second-client.ts
Normal file
13
backend/src/api/bitcoin/bitcoin-second-client.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import config from '../../config';
|
||||
import * as bitcoin from '@mempool/bitcoin';
|
||||
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
|
||||
|
||||
const nodeRpcCredentials: BitcoinRpcCredentials = {
|
||||
host: config.SECOND_CORE_RPC.HOST,
|
||||
port: config.SECOND_CORE_RPC.PORT,
|
||||
user: config.SECOND_CORE_RPC.USERNAME,
|
||||
pass: config.SECOND_CORE_RPC.PASSWORD,
|
||||
timeout: 60000,
|
||||
};
|
||||
|
||||
export default new bitcoin.Client(nodeRpcCredentials);
|
||||
@@ -1,10 +1,8 @@
|
||||
import config from '../../config';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import { IElectrumApi } from './electrum-api.interface';
|
||||
import BitcoinApi from './bitcoin-api';
|
||||
import mempool from '../mempool';
|
||||
import logger from '../../logger';
|
||||
import * as ElectrumClient from '@mempool/electrum-client';
|
||||
import * as sha256 from 'crypto-js/sha256';
|
||||
@@ -15,8 +13,8 @@ import memoryCache from '../memory-cache';
|
||||
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||
private electrumClient: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(bitcoinClient: any) {
|
||||
super(bitcoinClient);
|
||||
|
||||
const electrumConfig = { client: 'mempool-v2', version: '1.4' };
|
||||
const electrumPersistencePolicy = { retryPeriod: 10000, maxRetry: 1000, callback: null };
|
||||
@@ -44,7 +42,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||
}
|
||||
|
||||
async $getAddress(address: string): Promise<IEsploraApi.Address> {
|
||||
const addressInfo = await this.$validateAddress(address);
|
||||
const addressInfo = await this.bitcoindClient.validateAddress(address);
|
||||
if (!addressInfo || !addressInfo.isvalid) {
|
||||
return ({
|
||||
'address': address,
|
||||
@@ -89,16 +87,13 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||
},
|
||||
'electrum': true,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e === 'failed to get confirmed status') {
|
||||
e = 'The number of transactions on this address exceeds the Electrum server limit';
|
||||
}
|
||||
throw new Error(e);
|
||||
} catch (e: any) {
|
||||
throw new Error(typeof e === 'string' ? e : e && e.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
async $getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]> {
|
||||
const addressInfo = await this.$validateAddress(address);
|
||||
const addressInfo = await this.bitcoindClient.validateAddress(address);
|
||||
if (!addressInfo || !addressInfo.isvalid) {
|
||||
return [];
|
||||
}
|
||||
@@ -126,12 +121,9 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||
}
|
||||
|
||||
return transactions;
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
loadingIndicators.setProgress('address-' + address, 100);
|
||||
if (e === 'failed to get confirmed status') {
|
||||
e = 'The number of transactions on this address exceeds the Electrum server limit';
|
||||
}
|
||||
throw new Error(e);
|
||||
throw new Error(typeof e === 'string' ? e : e && e.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
$getAddressPrefix(prefix: string): string[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
$sendRawTransaction(rawTransaction: string): Promise<string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export default ElectrsApi;
|
||||
|
||||
@@ -6,10 +6,9 @@ import { BlockExtended, TransactionExtended } from '../mempool.interfaces';
|
||||
import { Common } from './common';
|
||||
import diskCache from './disk-cache';
|
||||
import transactionUtils from './transaction-utils';
|
||||
import bitcoinBaseApi from './bitcoin/bitcoin-base.api';
|
||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||
|
||||
class Blocks {
|
||||
private static INITIAL_BLOCK_AMOUNT = 8;
|
||||
private blocks: BlockExtended[] = [];
|
||||
private currentBlockHeight = 0;
|
||||
private currentDifficulty = 0;
|
||||
@@ -35,18 +34,18 @@ class Blocks {
|
||||
const blockHeightTip = await bitcoinApi.$getBlockHeightTip();
|
||||
|
||||
if (this.blocks.length === 0) {
|
||||
this.currentBlockHeight = blockHeightTip - Blocks.INITIAL_BLOCK_AMOUNT;
|
||||
this.currentBlockHeight = blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT;
|
||||
} else {
|
||||
this.currentBlockHeight = this.blocks[this.blocks.length - 1].height;
|
||||
}
|
||||
|
||||
if (blockHeightTip - this.currentBlockHeight > Blocks.INITIAL_BLOCK_AMOUNT * 2) {
|
||||
logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${Blocks.INITIAL_BLOCK_AMOUNT} recent blocks`);
|
||||
this.currentBlockHeight = blockHeightTip - Blocks.INITIAL_BLOCK_AMOUNT;
|
||||
if (blockHeightTip - this.currentBlockHeight > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 2) {
|
||||
logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.MEMPOOL.INITIAL_BLOCKS_AMOUNT} recent blocks`);
|
||||
this.currentBlockHeight = blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT;
|
||||
}
|
||||
|
||||
if (!this.lastDifficultyAdjustmentTime) {
|
||||
const blockchainInfo = await bitcoinBaseApi.$getBlockchainInfo();
|
||||
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
||||
if (blockchainInfo.blocks === blockchainInfo.headers) {
|
||||
const heightDiff = blockHeightTip % 2016;
|
||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);
|
||||
@@ -90,7 +89,7 @@ class Blocks {
|
||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
||||
transactions.push(tx);
|
||||
} catch (e) {
|
||||
logger.debug('Error fetching block tx: ' + e.message || e);
|
||||
logger.debug('Error fetching block tx: ' + (e instanceof Error ? e.message : e));
|
||||
if (i === 0) {
|
||||
throw new Error('Failed to fetch Coinbase transaction: ' + txIds[i]);
|
||||
}
|
||||
@@ -111,8 +110,8 @@ class Blocks {
|
||||
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||
transactions.shift();
|
||||
transactions.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
|
||||
blockExtended.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.effectiveFeePerVsize)) : 0;
|
||||
blockExtended.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions, 8) : [0, 0];
|
||||
blockExtended.medianFee = transactions.length > 0 ? Common.median(transactions.map((tx) => tx.effectiveFeePerVsize)) : 0;
|
||||
blockExtended.feeRange = transactions.length > 0 ? Common.getFeesInRange(transactions, 8) : [0, 0];
|
||||
|
||||
if (block.height % 2016 === 0) {
|
||||
this.previousDifficultyRetarget = (block.difficulty - this.currentDifficulty) / this.currentDifficulty * 100;
|
||||
@@ -121,8 +120,8 @@ class Blocks {
|
||||
}
|
||||
|
||||
this.blocks.push(blockExtended);
|
||||
if (this.blocks.length > Blocks.INITIAL_BLOCK_AMOUNT * 4) {
|
||||
this.blocks = this.blocks.slice(-Blocks.INITIAL_BLOCK_AMOUNT * 4);
|
||||
if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
|
||||
this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
|
||||
}
|
||||
|
||||
if (this.newBlockCallbacks.length) {
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
|
||||
|
||||
import config from '../config';
|
||||
export class Common {
|
||||
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
|
||||
'144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'
|
||||
: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
|
||||
static _isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet';
|
||||
|
||||
static isLiquid(): boolean {
|
||||
return this._isLiquid;
|
||||
}
|
||||
|
||||
static median(numbers: number[]) {
|
||||
let medianNr = 0;
|
||||
const numsLen = numbers.length;
|
||||
@@ -105,7 +114,7 @@ export class Common {
|
||||
totalFees += tx.bestDescendant.fee;
|
||||
}
|
||||
|
||||
tx.effectiveFeePerVsize = Math.max(1, totalFees / (totalWeight / 4));
|
||||
tx.effectiveFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1, totalFees / (totalWeight / 4));
|
||||
tx.cpfpChecked = true;
|
||||
|
||||
return {
|
||||
|
||||
178
backend/src/api/database-migration.ts
Normal file
178
backend/src/api/database-migration.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import config from '../config';
|
||||
import { DB } from '../database';
|
||||
import logger from '../logger';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 1;
|
||||
private queryTimeout = 120000;
|
||||
|
||||
constructor() { }
|
||||
|
||||
public async $initializeOrMigrateDatabase(): Promise<void> {
|
||||
if (!await this.$checkIfTableExists('statistics')) {
|
||||
await this.$initializeDatabaseTables();
|
||||
}
|
||||
|
||||
if (await this.$checkIfTableExists('state')) {
|
||||
const databaseSchemaVersion = await this.$getSchemaVersionFromDatabase();
|
||||
if (DatabaseMigration.currentVersion > databaseSchemaVersion) {
|
||||
await this.$migrateTableSchemaFromVersion(databaseSchemaVersion);
|
||||
}
|
||||
} else {
|
||||
await this.$migrateTableSchemaFromVersion(0);
|
||||
}
|
||||
}
|
||||
|
||||
private async $initializeDatabaseTables(): Promise<void> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
for (const query of this.getInitializeTableQueries()) {
|
||||
await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
}
|
||||
connection.release();
|
||||
logger.info(`Initial database tables have been created`);
|
||||
}
|
||||
|
||||
private async $migrateTableSchemaFromVersion(version: number): Promise<void> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
for (const query of this.getMigrationQueriesFromVersion(version)) {
|
||||
await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
}
|
||||
connection.release();
|
||||
await this.$updateToLatestSchemaVersion();
|
||||
logger.info(`Database schema have been migrated from version ${version} to ${DatabaseMigration.currentVersion} (latest version)`);
|
||||
}
|
||||
|
||||
private async $getSchemaVersionFromDatabase(): Promise<number> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT number FROM state WHERE name = 'schema_version';`;
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return rows[0]['number'];
|
||||
}
|
||||
|
||||
private async $updateToLatestSchemaVersion(): Promise<void> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `UPDATE state SET number = ${DatabaseMigration.currentVersion} WHERE name = 'schema_version'`;
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
}
|
||||
|
||||
private async $checkIfTableExists(table: string): Promise<boolean> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`;
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return rows[0]['COUNT(*)'] === 1;
|
||||
}
|
||||
|
||||
private getInitializeTableQueries(): string[] {
|
||||
const queries: string[] = [];
|
||||
|
||||
queries.push(`CREATE TABLE IF NOT EXISTS statistics (
|
||||
id int(11) NOT NULL,
|
||||
added datetime NOT NULL,
|
||||
unconfirmed_transactions int(11) UNSIGNED NOT NULL,
|
||||
tx_per_second float UNSIGNED NOT NULL,
|
||||
vbytes_per_second int(10) UNSIGNED NOT NULL,
|
||||
mempool_byte_weight int(10) UNSIGNED NOT NULL,
|
||||
fee_data longtext NOT NULL,
|
||||
total_fee double UNSIGNED NOT NULL,
|
||||
vsize_1 int(11) NOT NULL,
|
||||
vsize_2 int(11) NOT NULL,
|
||||
vsize_3 int(11) NOT NULL,
|
||||
vsize_4 int(11) NOT NULL,
|
||||
vsize_5 int(11) NOT NULL,
|
||||
vsize_6 int(11) NOT NULL,
|
||||
vsize_8 int(11) NOT NULL,
|
||||
vsize_10 int(11) NOT NULL,
|
||||
vsize_12 int(11) NOT NULL,
|
||||
vsize_15 int(11) NOT NULL,
|
||||
vsize_20 int(11) NOT NULL,
|
||||
vsize_30 int(11) NOT NULL,
|
||||
vsize_40 int(11) NOT NULL,
|
||||
vsize_50 int(11) NOT NULL,
|
||||
vsize_60 int(11) NOT NULL,
|
||||
vsize_70 int(11) NOT NULL,
|
||||
vsize_80 int(11) NOT NULL,
|
||||
vsize_90 int(11) NOT NULL,
|
||||
vsize_100 int(11) NOT NULL,
|
||||
vsize_125 int(11) NOT NULL,
|
||||
vsize_150 int(11) NOT NULL,
|
||||
vsize_175 int(11) NOT NULL,
|
||||
vsize_200 int(11) NOT NULL,
|
||||
vsize_250 int(11) NOT NULL,
|
||||
vsize_300 int(11) NOT NULL,
|
||||
vsize_350 int(11) NOT NULL,
|
||||
vsize_400 int(11) NOT NULL,
|
||||
vsize_500 int(11) NOT NULL,
|
||||
vsize_600 int(11) NOT NULL,
|
||||
vsize_700 int(11) NOT NULL,
|
||||
vsize_800 int(11) NOT NULL,
|
||||
vsize_900 int(11) NOT NULL,
|
||||
vsize_1000 int(11) NOT NULL,
|
||||
vsize_1200 int(11) NOT NULL,
|
||||
vsize_1400 int(11) NOT NULL,
|
||||
vsize_1600 int(11) NOT NULL,
|
||||
vsize_1800 int(11) NOT NULL,
|
||||
vsize_2000 int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
|
||||
|
||||
queries.push(`ALTER TABLE statistics ADD PRIMARY KEY (id);`);
|
||||
queries.push(`ALTER TABLE statistics MODIFY id int(11) NOT NULL AUTO_INCREMENT;`);
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
private getMigrationQueriesFromVersion(version: number): string[] {
|
||||
const queries: string[] = [];
|
||||
|
||||
if (version < 1) {
|
||||
if (config.MEMPOOL.NETWORK !== 'liquid' && config.MEMPOOL.NETWORK !== 'liquidtestnet') {
|
||||
queries.push(`UPDATE statistics SET
|
||||
vsize_1 = vsize_1 + vsize_2, vsize_2 = vsize_3,
|
||||
vsize_3 = vsize_4, vsize_4 = vsize_5,
|
||||
vsize_5 = vsize_6, vsize_6 = vsize_8,
|
||||
vsize_8 = vsize_10, vsize_10 = vsize_12,
|
||||
vsize_12 = vsize_15, vsize_15 = vsize_20,
|
||||
vsize_20 = vsize_30, vsize_30 = vsize_40,
|
||||
vsize_40 = vsize_50, vsize_50 = vsize_60,
|
||||
vsize_60 = vsize_70, vsize_70 = vsize_80,
|
||||
vsize_80 = vsize_90, vsize_90 = vsize_100,
|
||||
vsize_100 = vsize_125, vsize_125 = vsize_150,
|
||||
vsize_150 = vsize_175, vsize_175 = vsize_200,
|
||||
vsize_200 = vsize_250, vsize_250 = vsize_300,
|
||||
vsize_300 = vsize_350, vsize_350 = vsize_400,
|
||||
vsize_400 = vsize_500, vsize_500 = vsize_600,
|
||||
vsize_600 = vsize_700, vsize_700 = vsize_800,
|
||||
vsize_800 = vsize_900, vsize_900 = vsize_1000,
|
||||
vsize_1000 = vsize_1200, vsize_1200 = vsize_1400,
|
||||
vsize_1400 = vsize_1800, vsize_1800 = vsize_2000, vsize_2000 = 0`);
|
||||
}
|
||||
|
||||
queries.push(`CREATE TABLE IF NOT EXISTS elements_pegs (
|
||||
block int(11) NOT NULL,
|
||||
datetime int(11) NOT NULL,
|
||||
amount bigint(20) NOT NULL,
|
||||
txid varchar(65) NOT NULL,
|
||||
txindex int(11) NOT NULL,
|
||||
bitcoinaddress varchar(100) NOT NULL,
|
||||
bitcointxid varchar(65) NOT NULL,
|
||||
bitcoinindex int(11) NOT NULL,
|
||||
final_tx int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
|
||||
|
||||
queries.push(`CREATE TABLE IF NOT EXISTS state (
|
||||
name varchar(25) NOT NULL,
|
||||
number int(11) NULL,
|
||||
string varchar(100) NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
|
||||
|
||||
queries.push(`INSERT INTO state VALUES('schema_version', 0, NULL);`);
|
||||
queries.push(`INSERT INTO state VALUES('last_elements_block', 0, NULL);`);
|
||||
}
|
||||
|
||||
return queries;
|
||||
}
|
||||
}
|
||||
|
||||
export default new DatabaseMigration();
|
||||
@@ -52,7 +52,7 @@ class DiskCache {
|
||||
logger.debug('Mempool and blocks data saved to disk cache');
|
||||
this.isWritingCache = false;
|
||||
} catch (e) {
|
||||
logger.warn('Error writing to cache file: ' + e.message || e);
|
||||
logger.warn('Error writing to cache file: ' + (e instanceof Error ? e.message : e));
|
||||
this.isWritingCache = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import config from '../config';
|
||||
import { MempoolBlock } from '../mempool.interfaces';
|
||||
import { Common } from './common';
|
||||
import mempool from './mempool';
|
||||
import projectedBlocks from './mempool-blocks';
|
||||
|
||||
class FeeApi {
|
||||
constructor() { }
|
||||
|
||||
defaultFee = config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1;
|
||||
defaultFee = Common.isLiquid() ? 0.1 : 1;
|
||||
|
||||
public getRecommendedFee() {
|
||||
const pBlocks = projectedBlocks.getMempoolBlocks();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logger from '../logger';
|
||||
import axios from 'axios';
|
||||
import { IConversionRates } from '../mempool.interfaces';
|
||||
import config from '../config';
|
||||
|
||||
class FiatConversion {
|
||||
private conversionRates: IConversionRates = {
|
||||
@@ -16,7 +17,7 @@ class FiatConversion {
|
||||
|
||||
public startService() {
|
||||
logger.info('Starting currency rates service');
|
||||
setInterval(this.updateCurrency.bind(this), 1000 * 60);
|
||||
setInterval(this.updateCurrency.bind(this), 1000 * config.MEMPOOL.PRICE_FEED_UPDATE_INTERVAL);
|
||||
this.updateCurrency();
|
||||
}
|
||||
|
||||
@@ -35,7 +36,7 @@ class FiatConversion {
|
||||
this.ratesChangedCallback(this.conversionRates);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Error updating fiat conversion rates: ' + e);
|
||||
logger.err('Error updating fiat conversion rates: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
111
backend/src/api/liquid/elements-parser.ts
Normal file
111
backend/src/api/liquid/elements-parser.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { IBitcoinApi } from '../bitcoin/bitcoin-api.interface';
|
||||
import bitcoinClient from '../bitcoin/bitcoin-client';
|
||||
import bitcoinSecondClient from '../bitcoin/bitcoin-second-client';
|
||||
import { Common } from '../common';
|
||||
import { DB } from '../../database';
|
||||
import logger from '../../logger';
|
||||
|
||||
class ElementsParser {
|
||||
private isRunning = false;
|
||||
|
||||
constructor() { }
|
||||
|
||||
public async $parse() {
|
||||
if (this.isRunning) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.isRunning = true;
|
||||
const result = await bitcoinClient.getChainTips();
|
||||
const tip = result[0].height;
|
||||
const latestBlockHeight = await this.$getLatestBlockHeightFromDatabase();
|
||||
for (let height = latestBlockHeight + 1; height <= tip; height++) {
|
||||
const blockHash: IBitcoinApi.ChainTips = await bitcoinClient.getBlockHash(height);
|
||||
const block: IBitcoinApi.Block = await bitcoinClient.getBlock(blockHash, 2);
|
||||
await this.$parseBlock(block);
|
||||
await this.$saveLatestBlockToDatabase(block.height);
|
||||
}
|
||||
this.isRunning = false;
|
||||
} catch (e) {
|
||||
this.isRunning = false;
|
||||
throw new Error(e instanceof Error ? e.message : 'Error');
|
||||
}
|
||||
}
|
||||
|
||||
public async $getPegDataByMonth(): Promise<any> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT SUM(amount) AS amount, DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y-%m-01') AS date FROM elements_pegs GROUP BY DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y%m')`;
|
||||
const [rows] = await connection.query<any>(query);
|
||||
connection.release();
|
||||
return rows;
|
||||
}
|
||||
|
||||
protected async $parseBlock(block: IBitcoinApi.Block) {
|
||||
for (const tx of block.tx) {
|
||||
await this.$parseInputs(tx, block);
|
||||
await this.$parseOutputs(tx, block);
|
||||
}
|
||||
}
|
||||
|
||||
protected async $parseInputs(tx: IBitcoinApi.Transaction, block: IBitcoinApi.Block) {
|
||||
for (const [index, input] of tx.vin.entries()) {
|
||||
if (input.is_pegin) {
|
||||
await this.$parsePegIn(input, index, tx.txid, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async $parsePegIn(input: IBitcoinApi.Vin, vindex: number, txid: string, block: IBitcoinApi.Block) {
|
||||
const bitcoinTx: IBitcoinApi.Transaction = await bitcoinSecondClient.getRawTransaction(input.txid, true);
|
||||
const prevout = bitcoinTx.vout[input.vout || 0];
|
||||
const outputAddress = prevout.scriptPubKey.address || (prevout.scriptPubKey.addresses && prevout.scriptPubKey.addresses[0]) || '';
|
||||
await this.$savePegToDatabase(block.height, block.time, prevout.value * 100000000, txid, vindex,
|
||||
outputAddress, bitcoinTx.txid, prevout.n, 1);
|
||||
}
|
||||
|
||||
protected async $parseOutputs(tx: IBitcoinApi.Transaction, block: IBitcoinApi.Block) {
|
||||
for (const output of tx.vout) {
|
||||
if (output.scriptPubKey.pegout_chain) {
|
||||
await this.$savePegToDatabase(block.height, block.time, 0 - output.value * 100000000, tx.txid, output.n,
|
||||
(output.scriptPubKey.pegout_addresses && output.scriptPubKey.pegout_addresses[0] || ''), '', 0, 0);
|
||||
}
|
||||
if (!output.scriptPubKey.pegout_chain && output.scriptPubKey.type === 'nulldata'
|
||||
&& output.value && output.value > 0 && output.asset && output.asset === Common.nativeAssetId) {
|
||||
await this.$savePegToDatabase(block.height, block.time, 0 - output.value * 100000000, tx.txid, output.n,
|
||||
(output.scriptPubKey.pegout_addresses && output.scriptPubKey.pegout_addresses[0] || ''), '', 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async $savePegToDatabase(height: number, blockTime: number, amount: number, txid: string,
|
||||
txindex: number, bitcoinaddress: string, bitcointxid: string, bitcoinindex: number, final_tx: number): Promise<void> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `INSERT INTO elements_pegs(
|
||||
block, datetime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
const params: (string | number)[] = [
|
||||
height, blockTime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
|
||||
];
|
||||
await connection.query(query, params);
|
||||
connection.release();
|
||||
logger.debug(`Saved L-BTC peg from block height #${height} with TXID ${txid}.`);
|
||||
}
|
||||
|
||||
protected async $getLatestBlockHeightFromDatabase(): Promise<number> {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT number FROM state WHERE name = 'last_elements_block'`;
|
||||
const [rows] = await connection.query<any>(query);
|
||||
connection.release();
|
||||
return rows[0]['number'];
|
||||
}
|
||||
|
||||
protected async $saveLatestBlockToDatabase(blockHeight: number) {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `UPDATE state SET number = ? WHERE name = 'last_elements_block'`;
|
||||
await connection.query<any>(query, [blockHeight]);
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
export default new ElementsParser();
|
||||
39
backend/src/api/liquid/icons.ts
Normal file
39
backend/src/api/liquid/icons.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as fs from 'fs';
|
||||
import config from '../../config';
|
||||
import logger from '../../logger';
|
||||
|
||||
class Icons {
|
||||
private static FILE_NAME = './icons.json';
|
||||
private iconIds: string[] = [];
|
||||
private icons: { [assetId: string]: string; } = {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
public loadIcons() {
|
||||
if (!fs.existsSync(Icons.FILE_NAME)) {
|
||||
logger.warn(`${Icons.FILE_NAME} does not exist. No Liquid icons loaded.`);
|
||||
return;
|
||||
}
|
||||
const cacheData = fs.readFileSync(Icons.FILE_NAME, 'utf8');
|
||||
this.icons = JSON.parse(cacheData);
|
||||
|
||||
for (const i in this.icons) {
|
||||
this.iconIds.push(i);
|
||||
}
|
||||
logger.debug(`Liquid icons has been loaded.`);
|
||||
}
|
||||
|
||||
public getIconByAssetId(assetId: string): Buffer | undefined {
|
||||
const icon = this.icons[assetId];
|
||||
if (icon) {
|
||||
return Buffer.from(icon, 'base64');
|
||||
}
|
||||
}
|
||||
|
||||
public getAllIconIds() {
|
||||
return this.iconIds;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Icons();
|
||||
@@ -4,7 +4,6 @@ import { Common } from './common';
|
||||
import config from '../config';
|
||||
|
||||
class MempoolBlocks {
|
||||
private static DEFAULT_PROJECTED_BLOCKS_AMOUNT = 8;
|
||||
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||
|
||||
constructor() {}
|
||||
@@ -72,29 +71,30 @@ class MempoolBlocks {
|
||||
|
||||
private calculateMempoolBlocks(transactionsSorted: TransactionExtended[]): MempoolBlockWithTransactions[] {
|
||||
const mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||
let blockVSize = 0;
|
||||
let blockWeight = 0;
|
||||
let blockSize = 0;
|
||||
let transactions: TransactionExtended[] = [];
|
||||
transactionsSorted.forEach((tx) => {
|
||||
if (blockVSize + tx.vsize <= 1000000 || mempoolBlocks.length === MempoolBlocks.DEFAULT_PROJECTED_BLOCKS_AMOUNT - 1) {
|
||||
blockVSize += tx.vsize;
|
||||
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
|
||||
|| mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) {
|
||||
blockWeight += tx.weight;
|
||||
blockSize += tx.size;
|
||||
transactions.push(tx);
|
||||
} else {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockVSize, mempoolBlocks.length));
|
||||
blockVSize = tx.vsize;
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
||||
blockWeight = tx.weight;
|
||||
blockSize = tx.size;
|
||||
transactions = [tx];
|
||||
}
|
||||
});
|
||||
if (transactions.length) {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockVSize, mempoolBlocks.length));
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
||||
}
|
||||
return mempoolBlocks;
|
||||
}
|
||||
|
||||
private dataToMempoolBlocks(transactions: TransactionExtended[],
|
||||
blockSize: number, blockVSize: number, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
let rangeLength = 4;
|
||||
if (blocksIndex === 0) {
|
||||
rangeLength = 8;
|
||||
@@ -106,7 +106,7 @@ class MempoolBlocks {
|
||||
}
|
||||
return {
|
||||
blockSize: blockSize,
|
||||
blockVSize: blockVSize,
|
||||
blockVSize: blockWeight / 4,
|
||||
nTx: transactions.length,
|
||||
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||
|
||||
@@ -5,8 +5,9 @@ import logger from '../logger';
|
||||
import { Common } from './common';
|
||||
import transactionUtils from './transaction-utils';
|
||||
import { IBitcoinApi } from './bitcoin/bitcoin-api.interface';
|
||||
import bitcoinBaseApi from './bitcoin/bitcoin-base.api';
|
||||
import loadingIndicators from './loading-indicators';
|
||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||
|
||||
class Mempool {
|
||||
private static WEBSOCKET_REFRESH_RATE_MS = 10000;
|
||||
@@ -61,7 +62,7 @@ class Mempool {
|
||||
}
|
||||
|
||||
public async $updateMemPoolInfo() {
|
||||
this.mempoolInfo = await bitcoinBaseApi.$getMempoolInfo();
|
||||
this.mempoolInfo = await this.$getMempoolInfo();
|
||||
}
|
||||
|
||||
public getMempoolInfo(): IBitcoinApi.MempoolInfo {
|
||||
@@ -124,7 +125,7 @@ class Mempool {
|
||||
}
|
||||
newTransactions.push(transaction);
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction in mempool: ' + e.message || e);
|
||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +206,21 @@ class Mempool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private $getMempoolInfo() {
|
||||
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) {
|
||||
return Promise.all([
|
||||
bitcoinClient.getMempoolInfo(),
|
||||
bitcoinSecondClient.getMempoolInfo()
|
||||
]).then(([mempoolInfo, secondMempoolInfo]) => {
|
||||
mempoolInfo.maxmempool = secondMempoolInfo.maxmempool;
|
||||
mempoolInfo.mempoolminfee = secondMempoolInfo.mempoolminfee;
|
||||
mempoolInfo.minrelaytxfee = secondMempoolInfo.minrelaytxfee;
|
||||
return mempoolInfo;
|
||||
});
|
||||
}
|
||||
return bitcoinClient.getMempoolInfo();
|
||||
}
|
||||
}
|
||||
|
||||
export default new Mempool();
|
||||
|
||||
@@ -3,13 +3,15 @@ import { DB } from '../database';
|
||||
import logger from '../logger';
|
||||
|
||||
import { Statistic, TransactionExtended, OptimizedStatistic } from '../mempool.interfaces';
|
||||
import config from '../config';
|
||||
import { Common } from './common';
|
||||
|
||||
class Statistics {
|
||||
protected intervalTimer: NodeJS.Timer | undefined;
|
||||
protected newStatisticsEntryCallback: ((stats: OptimizedStatistic) => void) | undefined;
|
||||
protected queryTimeout = 120000;
|
||||
protected cache: { [date: string]: OptimizedStatistic[] } = {
|
||||
'24h': [], '1w': [], '1m': [], '3m': [], '6m': [], '1y': [],
|
||||
'24h': [], '1w': [], '1m': [], '3m': [], '6m': [], '1y': [], '2y': [], '3y': []
|
||||
};
|
||||
|
||||
public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
|
||||
@@ -48,6 +50,8 @@ class Statistics {
|
||||
this.cache['3m'] = await this.$list3M();
|
||||
this.cache['6m'] = await this.$list6M();
|
||||
this.cache['1y'] = await this.$list1Y();
|
||||
this.cache['2y'] = await this.$list2Y();
|
||||
this.cache['3y'] = await this.$list3Y();
|
||||
logger.debug('Statistics cache created');
|
||||
}
|
||||
|
||||
@@ -82,10 +86,15 @@ class Statistics {
|
||||
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
|
||||
|
||||
const weightVsizeFees: { [feePerWU: number]: number } = {};
|
||||
const lastItem = logFees.length - 1;
|
||||
|
||||
memPoolArray.forEach((transaction) => {
|
||||
for (let i = 0; i < logFees.length; i++) {
|
||||
if ((logFees[i] === 2000 && transaction.effectiveFeePerVsize >= 2000) || transaction.effectiveFeePerVsize <= logFees[i]) {
|
||||
if (
|
||||
(Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
|
||||
||
|
||||
(!Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize < logFees[i + 1]))
|
||||
) {
|
||||
if (weightVsizeFees[logFees[i]]) {
|
||||
weightVsizeFees[logFees[i]] += transaction.vsize;
|
||||
} else {
|
||||
@@ -255,12 +264,61 @@ class Statistics {
|
||||
connection.release();
|
||||
return result.insertId;
|
||||
} catch (e) {
|
||||
logger.err('$create() error' + e.message || e);
|
||||
logger.err('$create() error' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
private getQueryForDays(div: number) {
|
||||
return `SELECT id, added, unconfirmed_transactions,
|
||||
private getQueryForDaysAvg(div: number, interval: string) {
|
||||
return `SELECT id, UNIX_TIMESTAMP(added) as added,
|
||||
CAST(avg(unconfirmed_transactions) as FLOAT) as unconfirmed_transactions,
|
||||
CAST(avg(tx_per_second) as FLOAT) as tx_per_second,
|
||||
CAST(avg(vbytes_per_second) as FLOAT) as vbytes_per_second,
|
||||
CAST(avg(vsize_1) as FLOAT) as vsize_1,
|
||||
CAST(avg(vsize_2) as FLOAT) as vsize_2,
|
||||
CAST(avg(vsize_3) as FLOAT) as vsize_3,
|
||||
CAST(avg(vsize_4) as FLOAT) as vsize_4,
|
||||
CAST(avg(vsize_5) as FLOAT) as vsize_5,
|
||||
CAST(avg(vsize_6) as FLOAT) as vsize_6,
|
||||
CAST(avg(vsize_8) as FLOAT) as vsize_8,
|
||||
CAST(avg(vsize_10) as FLOAT) as vsize_10,
|
||||
CAST(avg(vsize_12) as FLOAT) as vsize_12,
|
||||
CAST(avg(vsize_15) as FLOAT) as vsize_15,
|
||||
CAST(avg(vsize_20) as FLOAT) as vsize_20,
|
||||
CAST(avg(vsize_30) as FLOAT) as vsize_30,
|
||||
CAST(avg(vsize_40) as FLOAT) as vsize_40,
|
||||
CAST(avg(vsize_50) as FLOAT) as vsize_50,
|
||||
CAST(avg(vsize_60) as FLOAT) as vsize_60,
|
||||
CAST(avg(vsize_70) as FLOAT) as vsize_70,
|
||||
CAST(avg(vsize_80) as FLOAT) as vsize_80,
|
||||
CAST(avg(vsize_90) as FLOAT) as vsize_90,
|
||||
CAST(avg(vsize_100) as FLOAT) as vsize_100,
|
||||
CAST(avg(vsize_125) as FLOAT) as vsize_125,
|
||||
CAST(avg(vsize_150) as FLOAT) as vsize_150,
|
||||
CAST(avg(vsize_175) as FLOAT) as vsize_175,
|
||||
CAST(avg(vsize_200) as FLOAT) as vsize_200,
|
||||
CAST(avg(vsize_250) as FLOAT) as vsize_250,
|
||||
CAST(avg(vsize_300) as FLOAT) as vsize_300,
|
||||
CAST(avg(vsize_350) as FLOAT) as vsize_350,
|
||||
CAST(avg(vsize_400) as FLOAT) as vsize_400,
|
||||
CAST(avg(vsize_500) as FLOAT) as vsize_500,
|
||||
CAST(avg(vsize_600) as FLOAT) as vsize_600,
|
||||
CAST(avg(vsize_700) as FLOAT) as vsize_700,
|
||||
CAST(avg(vsize_800) as FLOAT) as vsize_800,
|
||||
CAST(avg(vsize_900) as FLOAT) as vsize_900,
|
||||
CAST(avg(vsize_1000) as FLOAT) as vsize_1000,
|
||||
CAST(avg(vsize_1200) as FLOAT) as vsize_1200,
|
||||
CAST(avg(vsize_1400) as FLOAT) as vsize_1400,
|
||||
CAST(avg(vsize_1600) as FLOAT) as vsize_1600,
|
||||
CAST(avg(vsize_1800) as FLOAT) as vsize_1800,
|
||||
CAST(avg(vsize_2000) as FLOAT) as vsize_2000 \
|
||||
FROM statistics \
|
||||
WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
|
||||
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
|
||||
ORDER BY id DESC;`;
|
||||
}
|
||||
|
||||
private getQueryForDays(div: number, interval: string) {
|
||||
return `SELECT id, UNIX_TIMESTAMP(added) as added, unconfirmed_transactions,
|
||||
tx_per_second,
|
||||
vbytes_per_second,
|
||||
vsize_1,
|
||||
@@ -300,32 +358,36 @@ class Statistics {
|
||||
vsize_1400,
|
||||
vsize_1600,
|
||||
vsize_1800,
|
||||
vsize_2000 FROM statistics GROUP BY UNIX_TIMESTAMP(added) DIV ${div} ORDER BY id DESC LIMIT 480`;
|
||||
vsize_2000 \
|
||||
FROM statistics \
|
||||
WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
|
||||
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
|
||||
ORDER BY id DESC;`;
|
||||
}
|
||||
|
||||
public async $get(id: number): Promise<OptimizedStatistic | undefined> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT * FROM statistics WHERE id = ?`;
|
||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
|
||||
const [rows] = await connection.query<any>(query, [id]);
|
||||
connection.release();
|
||||
if (rows[0]) {
|
||||
return this.mapStatisticToOptimizedStatistic([rows[0]])[0];
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$list2H() error' + e.message || e);
|
||||
logger.err('$list2H() error' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
public async $list2H(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT * FROM statistics ORDER BY id DESC LIMIT 120`;
|
||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY id DESC LIMIT 120`;
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list2H() error' + e.message || e);
|
||||
logger.err('$list2H() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -333,12 +395,12 @@ class Statistics {
|
||||
public async $list24H(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(180);
|
||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY id DESC LIMIT 1440`;
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list24h() error' + e.message || e);
|
||||
logger.err('$list24h() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -346,12 +408,12 @@ class Statistics {
|
||||
public async $list1W(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(1260);
|
||||
const query = this.getQueryForDaysAvg(300, '1 WEEK'); // 5m interval
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list1W() error' + e);
|
||||
logger.err('$list1W() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -359,12 +421,12 @@ class Statistics {
|
||||
public async $list1M(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(5040);
|
||||
const query = this.getQueryForDaysAvg(1800, '1 MONTH'); // 30m interval
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list1M() error' + e);
|
||||
logger.err('$list1M() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -372,12 +434,12 @@ class Statistics {
|
||||
public async $list3M(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(15120);
|
||||
const query = this.getQueryForDaysAvg(7200, '3 MONTH'); // 2h interval
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list3M() error' + e);
|
||||
logger.err('$list3M() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -385,12 +447,12 @@ class Statistics {
|
||||
public async $list6M(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(30240);
|
||||
const query = this.getQueryForDaysAvg(10800, '6 MONTH'); // 3h interval
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list6M() error' + e);
|
||||
logger.err('$list6M() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -398,15 +460,42 @@ class Statistics {
|
||||
public async $list1Y(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(60480);
|
||||
const query = this.getQueryForDays(28800, '1 YEAR'); // 8h interval
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list6M() error' + e);
|
||||
logger.err('$list1Y() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async $list2Y(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(28800, "2 YEAR"); // 8h interval
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list2Y() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async $list3Y(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(43200, "3 YEAR"); // 12h interval
|
||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||
connection.release();
|
||||
return this.mapStatisticToOptimizedStatistic(rows);
|
||||
} catch (e) {
|
||||
logger.err('$list3Y() error' + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private mapStatisticToOptimizedStatistic(statistic: Statistic[]): OptimizedStatistic[] {
|
||||
return statistic.map((s) => {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import logger from '../logger';
|
||||
import { TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
|
||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
||||
import config from '../config';
|
||||
import { Common } from './common';
|
||||
|
||||
class TransactionUtils {
|
||||
constructor() { }
|
||||
@@ -31,7 +32,8 @@ class TransactionUtils {
|
||||
// @ts-ignore
|
||||
return transaction;
|
||||
}
|
||||
const feePerVbytes = Math.max(1, (transaction.fee || 0) / (transaction.weight / 4));
|
||||
const feePerVbytes = Math.max(Common.isLiquid() ? 0.1 : 1,
|
||||
(transaction.fee || 0) / (transaction.weight / 4));
|
||||
const transactionExtended: TransactionExtended = Object.assign({
|
||||
vsize: Math.round(transaction.weight / 4),
|
||||
feePerVsize: feePerVbytes,
|
||||
|
||||
@@ -14,7 +14,6 @@ import transactionUtils from './transaction-utils';
|
||||
|
||||
class WebsocketHandler {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
private nativeAssetId = '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
|
||||
private extraInitProperties = {};
|
||||
|
||||
constructor() { }
|
||||
@@ -61,7 +60,7 @@ class WebsocketHandler {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||
response['tx'] = fullTx;
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction: ' + e.message || e);
|
||||
logger.debug('Error finding transaction: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -69,7 +68,7 @@ class WebsocketHandler {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(client['track-tx'], true);
|
||||
response['tx'] = fullTx;
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction. ' + e.message || e);
|
||||
logger.debug('Error finding transaction. ' + (e instanceof Error ? e.message : e));
|
||||
client['track-mempool-tx'] = parsedMessage['track-tx'];
|
||||
}
|
||||
}
|
||||
@@ -80,9 +79,13 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (parsedMessage && parsedMessage['track-address']) {
|
||||
if (/^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,87})$/
|
||||
if (/^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100})$/
|
||||
.test(parsedMessage['track-address'])) {
|
||||
client['track-address'] = parsedMessage['track-address'];
|
||||
let matchedAddress = parsedMessage['track-address'];
|
||||
if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}$/.test(parsedMessage['track-address'])) {
|
||||
matchedAddress = matchedAddress.toLowerCase();
|
||||
}
|
||||
client['track-address'] = matchedAddress;
|
||||
} else {
|
||||
client['track-address'] = null;
|
||||
}
|
||||
@@ -97,7 +100,7 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (parsedMessage.action === 'init') {
|
||||
const _blocks = blocks.getBlocks().slice(-8);
|
||||
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
||||
if (!_blocks) {
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +127,7 @@ class WebsocketHandler {
|
||||
client.send(JSON.stringify(response));
|
||||
}
|
||||
} catch (e) {
|
||||
logger.debug('Error parsing websocket message: ' + e.message || e);
|
||||
logger.debug('Error parsing websocket message: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -173,7 +176,7 @@ class WebsocketHandler {
|
||||
|
||||
getInitData(_blocks?: BlockExtended[]) {
|
||||
if (!_blocks) {
|
||||
_blocks = blocks.getBlocks().slice(-8);
|
||||
_blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
||||
}
|
||||
return {
|
||||
'mempoolInfo': memPool.getMempoolInfo(),
|
||||
@@ -252,7 +255,7 @@ class WebsocketHandler {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||
response['tx'] = fullTx;
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction in mempool: ' + e.message || e);
|
||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
} else {
|
||||
response['tx'] = tx;
|
||||
@@ -272,7 +275,7 @@ class WebsocketHandler {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||
foundTransactions.push(fullTx);
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction in mempool: ' + e.message || e);
|
||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
} else {
|
||||
foundTransactions.push(tx);
|
||||
@@ -286,7 +289,7 @@ class WebsocketHandler {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||
foundTransactions.push(fullTx);
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction in mempool: ' + e.message || e);
|
||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
} else {
|
||||
foundTransactions.push(tx);
|
||||
@@ -304,7 +307,7 @@ class WebsocketHandler {
|
||||
|
||||
newTransactions.forEach((tx) => {
|
||||
|
||||
if (client['track-asset'] === this.nativeAssetId) {
|
||||
if (client['track-asset'] === Common.nativeAssetId) {
|
||||
if (tx.vin.some((vin) => !!vin.is_pegin)) {
|
||||
foundTransactions.push(tx);
|
||||
return;
|
||||
@@ -337,7 +340,7 @@ class WebsocketHandler {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(rbfTransaction, true);
|
||||
response['rbfTransaction'] = fullTx;
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction in mempool: ' + e.message || e);
|
||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
} else {
|
||||
response['rbfTransaction'] = rbfTx;
|
||||
@@ -435,7 +438,7 @@ class WebsocketHandler {
|
||||
const foundTransactions: TransactionExtended[] = [];
|
||||
|
||||
transactions.forEach((tx) => {
|
||||
if (client['track-asset'] === this.nativeAssetId) {
|
||||
if (client['track-asset'] === Common.nativeAssetId) {
|
||||
if (tx.vin && tx.vin.some((vin) => !!vin.is_pegin)) {
|
||||
foundTransactions.push(tx);
|
||||
return;
|
||||
|
||||
@@ -2,7 +2,7 @@ const configFile = require('../mempool-config.json');
|
||||
|
||||
interface IConfig {
|
||||
MEMPOOL: {
|
||||
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid';
|
||||
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
|
||||
BACKEND: 'esplora' | 'electrum' | 'none';
|
||||
HTTP_PORT: number;
|
||||
SPAWN_CLUSTER_PROCS: number;
|
||||
@@ -11,6 +11,12 @@ interface IConfig {
|
||||
CACHE_DIR: string;
|
||||
CLEAR_PROTECTION_MINUTES: number;
|
||||
RECOMMENDED_FEE_PERCENTILE: number;
|
||||
BLOCK_WEIGHT_UNITS: number;
|
||||
INITIAL_BLOCKS_AMOUNT: number;
|
||||
MEMPOOL_BLOCKS_AMOUNT: number;
|
||||
PRICE_FEED_UPDATE_INTERVAL: number;
|
||||
USE_SECOND_NODE_FOR_MINFEE: boolean;
|
||||
EXTERNAL_ASSETS: string[];
|
||||
};
|
||||
ESPLORA: {
|
||||
REST_API_URL: string;
|
||||
@@ -26,8 +32,7 @@ interface IConfig {
|
||||
USERNAME: string;
|
||||
PASSWORD: string;
|
||||
};
|
||||
CORE_RPC_MINFEE: {
|
||||
ENABLED: boolean;
|
||||
SECOND_CORE_RPC: {
|
||||
HOST: string;
|
||||
PORT: number;
|
||||
USERNAME: string;
|
||||
@@ -69,6 +74,12 @@ const defaults: IConfig = {
|
||||
'CACHE_DIR': './cache',
|
||||
'CLEAR_PROTECTION_MINUTES': 20,
|
||||
'RECOMMENDED_FEE_PERCENTILE': 50,
|
||||
'BLOCK_WEIGHT_UNITS': 4000000,
|
||||
'INITIAL_BLOCKS_AMOUNT': 8,
|
||||
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
||||
'PRICE_FEED_UPDATE_INTERVAL': 3600,
|
||||
'USE_SECOND_NODE_FOR_MINFEE': false,
|
||||
'EXTERNAL_ASSETS': [],
|
||||
},
|
||||
'ESPLORA': {
|
||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
@@ -84,8 +95,7 @@ const defaults: IConfig = {
|
||||
'USERNAME': 'mempool',
|
||||
'PASSWORD': 'mempool'
|
||||
},
|
||||
'CORE_RPC_MINFEE': {
|
||||
'ENABLED': false,
|
||||
'SECOND_CORE_RPC': {
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': 8332,
|
||||
'USERNAME': 'mempool',
|
||||
@@ -121,7 +131,7 @@ class Config implements IConfig {
|
||||
ESPLORA: IConfig['ESPLORA'];
|
||||
ELECTRUM: IConfig['ELECTRUM'];
|
||||
CORE_RPC: IConfig['CORE_RPC'];
|
||||
CORE_RPC_MINFEE: IConfig['CORE_RPC_MINFEE'];
|
||||
SECOND_CORE_RPC: IConfig['SECOND_CORE_RPC'];
|
||||
DATABASE: IConfig['DATABASE'];
|
||||
SYSLOG: IConfig['SYSLOG'];
|
||||
STATISTICS: IConfig['STATISTICS'];
|
||||
@@ -133,7 +143,7 @@ class Config implements IConfig {
|
||||
this.ESPLORA = configs.ESPLORA;
|
||||
this.ELECTRUM = configs.ELECTRUM;
|
||||
this.CORE_RPC = configs.CORE_RPC;
|
||||
this.CORE_RPC_MINFEE = configs.CORE_RPC_MINFEE;
|
||||
this.SECOND_CORE_RPC = configs.SECOND_CORE_RPC;
|
||||
this.DATABASE = configs.DATABASE;
|
||||
this.SYSLOG = configs.SYSLOG;
|
||||
this.STATISTICS = configs.STATISTICS;
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function checkDbConnection() {
|
||||
logger.info('Database connection established.');
|
||||
connection.release();
|
||||
} catch (e) {
|
||||
logger.err('Could not connect to database: ' + e.message || e);
|
||||
logger.err('Could not connect to database: ' + (e instanceof Error ? e.message : e));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ import logger from './logger';
|
||||
import backendInfo from './api/backend-info';
|
||||
import loadingIndicators from './api/loading-indicators';
|
||||
import mempool from './api/mempool';
|
||||
import elementsParser from './api/liquid/elements-parser';
|
||||
import databaseMigration from './api/database-migration';
|
||||
import syncAssets from './sync-assets';
|
||||
import icons from './api/liquid/icons';
|
||||
import { Common } from './api/common';
|
||||
|
||||
class Server {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
@@ -68,23 +73,34 @@ class Server {
|
||||
next();
|
||||
})
|
||||
.use(express.urlencoded({ extended: true }))
|
||||
.use(express.json());
|
||||
.use(express.text())
|
||||
;
|
||||
|
||||
this.server = http.createServer(this.app);
|
||||
this.wss = new WebSocket.Server({ server: this.server });
|
||||
|
||||
this.setUpWebsocketHandling();
|
||||
|
||||
await syncAssets.syncAssets();
|
||||
diskCache.loadMempoolCache();
|
||||
|
||||
if (config.DATABASE.ENABLED) {
|
||||
await checkDbConnection();
|
||||
try {
|
||||
await databaseMigration.$initializeOrMigrateDatabase();
|
||||
} catch (e) {
|
||||
throw new Error(e instanceof Error ? e.message : 'Error');
|
||||
}
|
||||
}
|
||||
|
||||
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isMaster) {
|
||||
statistics.startStatistics();
|
||||
}
|
||||
|
||||
if (Common.isLiquid()) {
|
||||
icons.loadIcons();
|
||||
}
|
||||
|
||||
fiatConversion.startService();
|
||||
|
||||
this.setUpHttpApiRoutes();
|
||||
@@ -111,8 +127,8 @@ class Server {
|
||||
try {
|
||||
await memPool.$updateMemPoolInfo();
|
||||
} catch (e) {
|
||||
const msg = `updateMempoolInfo: ${(e.message || e)}`;
|
||||
if (config.CORE_RPC_MINFEE.ENABLED) {
|
||||
const msg = `updateMempoolInfo: ${(e instanceof Error ? e.message : e)}`;
|
||||
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) {
|
||||
logger.warn(msg);
|
||||
} else {
|
||||
logger.debug(msg);
|
||||
@@ -123,7 +139,7 @@ class Server {
|
||||
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
|
||||
this.currentBackendRetryInterval = 5;
|
||||
} catch (e) {
|
||||
const loggerMsg = `runMainLoop error: ${(e.message || e)}. Retrying in ${this.currentBackendRetryInterval} sec.`;
|
||||
const loggerMsg = `runMainLoop error: ${(e instanceof Error ? e.message : e)}. Retrying in ${this.currentBackendRetryInterval} sec.`;
|
||||
if (this.currentBackendRetryInterval > 5) {
|
||||
logger.warn(loggerMsg);
|
||||
mempool.setOutOfSync();
|
||||
@@ -141,6 +157,15 @@ class Server {
|
||||
if (this.wss) {
|
||||
websocketHandler.setWebsocketServer(this.wss);
|
||||
}
|
||||
if (Common.isLiquid() && config.DATABASE.ENABLED) {
|
||||
blocks.setNewBlockCallback(async () => {
|
||||
try {
|
||||
await elementsParser.$parse();
|
||||
} catch (e) {
|
||||
logger.warn('Elements parsing error: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
});
|
||||
}
|
||||
websocketHandler.setupConnectionHandling();
|
||||
statistics.setNewStatisticsEntryCallback(websocketHandler.handleNewStatistic.bind(websocketHandler));
|
||||
blocks.setNewBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler));
|
||||
@@ -158,6 +183,8 @@ class Server {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fees/mempool-blocks', routes.getMempoolBlocks)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', routes.getBackendInfo)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', routes.getInitData)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', routes.validateAddress)
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', routes.$postTransactionForm)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
||||
try {
|
||||
const response = await axios.get('https://mempool.space/api/v1/donations', { responseType: 'stream', timeout: 10000 });
|
||||
@@ -205,6 +232,8 @@ class Server {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3m', routes.get3MStatistics.bind(routes))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/6m', routes.get6MStatistics.bind(routes))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.get1YStatistics.bind(routes))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.get2YStatistics.bind(routes))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.get3YStatistics.bind(routes))
|
||||
;
|
||||
}
|
||||
|
||||
@@ -235,6 +264,7 @@ class Server {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mempool/txids', routes.getMempoolTxIds)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', routes.getRecentMempoolTransactions)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', routes.getTransaction)
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'tx', routes.$postTransaction)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', routes.getRawTransaction)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', routes.getTransactionStatus)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', routes.getTransactionOutspends)
|
||||
@@ -253,6 +283,19 @@ class Server {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', routes.getAddressPrefix)
|
||||
;
|
||||
}
|
||||
|
||||
if (Common.isLiquid()) {
|
||||
this.app
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
|
||||
;
|
||||
}
|
||||
|
||||
if (Common.isLiquid() && config.DATABASE.ENABLED) {
|
||||
this.app
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ import transactionUtils from './api/transaction-utils';
|
||||
import blocks from './api/blocks';
|
||||
import loadingIndicators from './api/loading-indicators';
|
||||
import { Common } from './api/common';
|
||||
import bitcoinClient from './api/bitcoin/bitcoin-client';
|
||||
import elementsParser from './api/liquid/elements-parser';
|
||||
import icons from './api/liquid/icons';
|
||||
|
||||
class Routes {
|
||||
constructor() {}
|
||||
@@ -50,12 +53,20 @@ class Routes {
|
||||
res.json(statistics.getCache()['1y']);
|
||||
}
|
||||
|
||||
public get2YStatistics(req: Request, res: Response) {
|
||||
res.json(statistics.getCache()['2y']);
|
||||
}
|
||||
|
||||
public get3YStatistics(req: Request, res: Response) {
|
||||
res.json(statistics.getCache()['3y']);
|
||||
}
|
||||
|
||||
public getInitData(req: Request, res: Response) {
|
||||
try {
|
||||
const result = websocketHandler.getInitData();
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +85,7 @@ class Routes {
|
||||
const result = mempoolBlocks.getMempoolBlocks();
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,10 +488,10 @@ class Routes {
|
||||
res.json(transaction);
|
||||
} catch (e) {
|
||||
let statusCode = 500;
|
||||
if (e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
if (e instanceof Error && e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
statusCode = 404;
|
||||
}
|
||||
res.status(statusCode).send(e.message || e);
|
||||
res.status(statusCode).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,10 +502,10 @@ class Routes {
|
||||
res.send(transaction.hex);
|
||||
} catch (e) {
|
||||
let statusCode = 500;
|
||||
if (e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
statusCode = 404;
|
||||
}
|
||||
res.status(statusCode).send(e.message || e);
|
||||
res.status(statusCode).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,10 +515,10 @@ class Routes {
|
||||
res.json(transaction.status);
|
||||
} catch (e) {
|
||||
let statusCode = 500;
|
||||
if (e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
statusCode = 404;
|
||||
}
|
||||
res.status(statusCode).send(e.message || e);
|
||||
res.status(statusCode).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,7 +527,7 @@ class Routes {
|
||||
const result = await bitcoinApi.$getBlock(req.params.hash);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +537,7 @@ class Routes {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.send(blockHeader);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +574,7 @@ class Routes {
|
||||
res.json(returnBlocks);
|
||||
} catch (e) {
|
||||
loadingIndicators.setProgress('blocks', 100);
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,13 +593,13 @@ class Routes {
|
||||
transactions.push(transaction);
|
||||
loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i + 1) / endIndex * 100);
|
||||
} catch (e) {
|
||||
logger.debug('getBlockTransactions error: ' + e.message || e);
|
||||
logger.debug('getBlockTransactions error: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
res.json(transactions);
|
||||
} catch (e) {
|
||||
loadingIndicators.setProgress('blocktxs-' + req.params.hash, 100);
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +608,7 @@ class Routes {
|
||||
const blockHash = await bitcoinApi.$getBlockHash(parseInt(req.params.height, 10));
|
||||
res.send(blockHash);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,10 +622,10 @@ class Routes {
|
||||
const addressData = await bitcoinApi.$getAddress(req.params.address);
|
||||
res.json(addressData);
|
||||
} catch (e) {
|
||||
if (e.message && e.message.indexOf('exceeds') > 0) {
|
||||
return res.status(413).send(e.message);
|
||||
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
|
||||
return res.status(413).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,10 +639,10 @@ class Routes {
|
||||
const transactions = await bitcoinApi.$getAddressTransactions(req.params.address, req.params.txId);
|
||||
res.json(transactions);
|
||||
} catch (e) {
|
||||
if (e.message && e.message.indexOf('exceeds') > 0) {
|
||||
return res.status(413).send(e.message);
|
||||
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
|
||||
return res.status(413).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,7 +655,7 @@ class Routes {
|
||||
const blockHash = await bitcoinApi.$getAddressPrefix(req.params.prefix);
|
||||
res.send(blockHash);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,7 +676,7 @@ class Routes {
|
||||
const rawMempool = await bitcoinApi.$getRawMempool();
|
||||
res.send(rawMempool);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,7 +685,7 @@ class Routes {
|
||||
const result = await bitcoinApi.$getBlockHeightTip();
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +694,16 @@ class Routes {
|
||||
const result = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
public async validateAddress(req: Request, res: Response) {
|
||||
try {
|
||||
const result = await bitcoinClient.validateAddress(req.params.address);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,42 +713,119 @@ class Routes {
|
||||
|
||||
public getDifficultyChange(req: Request, res: Response) {
|
||||
try {
|
||||
const now = new Date().getTime() / 1000;
|
||||
const DATime = blocks.getLastDifficultyAdjustmentTime();
|
||||
const previousRetarget = blocks.getPreviousDifficultyRetarget();
|
||||
const diff = now - DATime;
|
||||
const blockHeight = blocks.getCurrentBlockHeight();
|
||||
|
||||
const now = new Date().getTime() / 1000;
|
||||
const diff = now - DATime;
|
||||
const blocksInEpoch = blockHeight % 2016;
|
||||
const difficultyChange = (600 / (diff / blocksInEpoch) - 1) * 100;
|
||||
const progressPercent = (blocksInEpoch >= 0) ? blocksInEpoch / 2016 * 100 : 100;
|
||||
const remainingBlocks = 2016 - blocksInEpoch;
|
||||
const nextRetargetHeight = blockHeight + remainingBlocks;
|
||||
|
||||
let difficultyChange = 0;
|
||||
if (remainingBlocks < 1870) {
|
||||
if (blocksInEpoch > 0) {
|
||||
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
|
||||
}
|
||||
if (difficultyChange > 300) {
|
||||
difficultyChange = 300;
|
||||
}
|
||||
if (difficultyChange < -75) {
|
||||
difficultyChange = -75;
|
||||
}
|
||||
}
|
||||
|
||||
const timeAvgDiff = difficultyChange * 0.1;
|
||||
|
||||
let timeAvgMins = 10;
|
||||
if (timeAvgDiff > 0 ){
|
||||
if (timeAvgDiff > 0) {
|
||||
timeAvgMins -= Math.abs(timeAvgDiff);
|
||||
} else {
|
||||
timeAvgMins += Math.abs(timeAvgDiff);
|
||||
}
|
||||
|
||||
const remainingBlocks = 2016 - blocksInEpoch;
|
||||
const timeAvgSeconds = timeAvgMins * 60;
|
||||
const remainingTime = remainingBlocks * timeAvgSeconds;
|
||||
const estimatedRetargetDate = (remainingTime + now);
|
||||
const totalTime = estimatedRetargetDate-DATime;
|
||||
const progressPercent = 100 - ((remainingTime * 100) / totalTime);
|
||||
const timeAvg = timeAvgMins * 60;
|
||||
const remainingTime = remainingBlocks * timeAvg;
|
||||
const estimatedRetargetDate = remainingTime + now;
|
||||
|
||||
const result={
|
||||
const result = {
|
||||
progressPercent,
|
||||
difficultyChange,
|
||||
estimatedRetargetDate,
|
||||
remainingBlocks,
|
||||
remainingTime,
|
||||
previousRetarget,
|
||||
}
|
||||
nextRetargetHeight,
|
||||
timeAvg,
|
||||
};
|
||||
res.json(result);
|
||||
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message || e);
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
public async $getElementsPegsByMonth(req: Request, res: Response) {
|
||||
try {
|
||||
const pegs = await elementsParser.$getPegDataByMonth();
|
||||
res.json(pegs);
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
public async $postTransaction(req: Request, res: Response) {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
try {
|
||||
let rawTx;
|
||||
if (typeof req.body === 'object') {
|
||||
rawTx = Object.keys(req.body)[0];
|
||||
} else {
|
||||
rawTx = req.body;
|
||||
}
|
||||
const txIdResult = await bitcoinApi.$sendRawTransaction(rawTx);
|
||||
res.send(txIdResult);
|
||||
} catch (e: any) {
|
||||
res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||
: (e.message || 'Error'));
|
||||
}
|
||||
}
|
||||
|
||||
public async $postTransactionForm(req: Request, res: Response) {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
const matches = /tx=([a-z0-9]+)/.exec(req.body);
|
||||
let txHex = '';
|
||||
if (matches && matches[1]) {
|
||||
txHex = matches[1];
|
||||
}
|
||||
try {
|
||||
const txIdResult = await bitcoinClient.sendRawTransaction(txHex);
|
||||
res.send(txIdResult);
|
||||
} catch (e: any) {
|
||||
res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||
: (e.message || 'Error'));
|
||||
}
|
||||
}
|
||||
|
||||
public getLiquidIcon(req: Request, res: Response) {
|
||||
const result = icons.getIconByAssetId(req.params.assetId);
|
||||
if (result) {
|
||||
res.setHeader('content-type', 'image/png');
|
||||
res.setHeader('content-length', result.length);
|
||||
res.send(result);
|
||||
} else {
|
||||
res.status(404).send('Asset icon not found');
|
||||
}
|
||||
}
|
||||
|
||||
public getAllLiquidIcon(req: Request, res: Response) {
|
||||
const result = icons.getAllIconIds();
|
||||
if (result) {
|
||||
res.json(result);
|
||||
} else {
|
||||
res.status(404).send('Asset icons not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
backend/src/sync-assets.ts
Normal file
32
backend/src/sync-assets.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import axios from 'axios';
|
||||
import * as fs from 'fs';
|
||||
const fsPromises = fs.promises;
|
||||
import config from './config';
|
||||
import logger from './logger';
|
||||
|
||||
const PATH = './';
|
||||
|
||||
class SyncAssets {
|
||||
constructor() { }
|
||||
|
||||
public async syncAssets() {
|
||||
for (const url of config.MEMPOOL.EXTERNAL_ASSETS) {
|
||||
await this.downloadFile(url);
|
||||
}
|
||||
}
|
||||
|
||||
private async downloadFile(url: string) {
|
||||
const fileName = url.split('/').slice(-1)[0];
|
||||
logger.info(`Downloading external asset: ${fileName}...`);
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
responseType: 'stream', timeout: 30000
|
||||
});
|
||||
await fsPromises.writeFile(PATH + fileName, response.data);
|
||||
} catch (e: any) {
|
||||
throw new Error(`Failed to download external asset. ` + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new SyncAssets();
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"lib": ["es2019"],
|
||||
"lib": ["es2019", "dom"],
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
In an empty dir create 2 sub-dirs
|
||||
|
||||
```bash
|
||||
mkdir -p data mysql/data mysql/db-scripts
|
||||
mkdir -p data mysql/data
|
||||
```
|
||||
|
||||
In the `mysql/db-scripts` sub-dir add the `mariadb-structure.sql` file from the mempool repo
|
||||
|
||||
Your dir should now look like that:
|
||||
|
||||
```bash
|
||||
@@ -23,9 +21,6 @@ data mysql
|
||||
data db-scripts
|
||||
|
||||
./mysql/data:
|
||||
|
||||
./mysql/db-scripts:
|
||||
mariadb-structure.sql
|
||||
```
|
||||
|
||||
In the main dir add the following `docker-compose.yml`
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
FROM node:12-buster-slim AS builder
|
||||
FROM node:16.10.0-buster-slim AS builder
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y build-essential python3 pkg-config
|
||||
RUN npm ci --production
|
||||
RUN npm i typescript
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
FROM node:12-buster-slim
|
||||
FROM node:16.10.0-buster-slim
|
||||
|
||||
WORKDIR /backend
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:12-buster-slim AS builder
|
||||
FROM node:16.10.0-buster-slim AS builder
|
||||
|
||||
ARG commitHash
|
||||
ENV DOCKER_COMMIT_HASH=${commitHash}
|
||||
|
||||
18
docker/scripts/get_image_digest.sh
Executable file
18
docker/scripts/get_image_digest.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
|
||||
VERSION=$1
|
||||
IMAGE=""
|
||||
|
||||
if [ -z "${VERSION}" ]; then
|
||||
echo "no version provided (i.e, v2.2.0), using latest tag"
|
||||
VERSION="latest"
|
||||
fi
|
||||
|
||||
for package in frontend backend; do
|
||||
PACKAGE=mempool/"$package"
|
||||
IMAGE="$PACKAGE":"$VERSION"
|
||||
HASH=`docker pull $IMAGE > /dev/null && docker inspect $IMAGE | sed -n '/RepoDigests/{n;p;}' | grep -o '[0-9a-f]\{64\}'`
|
||||
if [ -n "${HASH}" ]; then
|
||||
echo "$IMAGE"@sha256:"$HASH"
|
||||
fi
|
||||
done
|
||||
6
frontend/.gitignore
vendored
6
frontend/.gitignore
vendored
@@ -34,6 +34,7 @@ speed-measure-plugin.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
@@ -49,6 +50,8 @@ Thumbs.db
|
||||
|
||||
src/resources/assets.json
|
||||
src/resources/assets.minimal.json
|
||||
src/resources/assets-testnet.json
|
||||
src/resources/assets-testnet.minimal.json
|
||||
src/resources/pools.json
|
||||
|
||||
# environment config
|
||||
@@ -59,3 +62,6 @@ generated-config.js
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
|
||||
# Base index
|
||||
src/index.html
|
||||
|
||||
|
||||
@@ -1,11 +1,50 @@
|
||||
# mempool-frontend
|
||||
|
||||
## Transifex Project
|
||||
## Contributing
|
||||
|
||||
This package is used for the https://mempool.space, https://liquid.network and https://bisq.markets websites - there are npm scripts to setup all three, which effectively change how BASE_MODULE is configured:
|
||||
|
||||
```
|
||||
$ npm run config:defaults:mempool
|
||||
$ npm run config:defaults:liquid
|
||||
$ npm run config:defaults:bisq
|
||||
```
|
||||
|
||||
Changes that affect the frontend codebase only can be done using the production backend so you don't need to spin up the entire Mempool infrastructure. This is very convenient in case you want to quickly improve the UI, fix typos or implement new features that don't require any backend changes.
|
||||
|
||||
Make your changes, install the project dependencies and run the frontend server as follows:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
$ npm run serve:local-prod
|
||||
```
|
||||
|
||||
The frontend will be available at http://localhost:4200/ and all API requests will be proxied to the production server at https://mempool.space
|
||||
|
||||
After making your changes, you can run our end-to-end automation suite and check for possible regressions:
|
||||
|
||||
Headless:
|
||||
|
||||
```
|
||||
$ npm run config:defaults:mempool && npm run cypress:run
|
||||
```
|
||||
|
||||
Interactive:
|
||||
|
||||
```
|
||||
$ npm run config:defaults:mempool && npm run cypress:open
|
||||
```
|
||||
|
||||
This will open the Cypress test runner, where you can select any of the test files to run.
|
||||
|
||||
If all tests are green, submit your PR and it will be reviewed by someone on the team as soon as possible.
|
||||
|
||||
## Translations: Transifex Project
|
||||
|
||||
The mempool frontend strings are localized into 20+ locales:
|
||||
https://www.transifex.com/mempool/mempool/dashboard/
|
||||
|
||||
## Translators
|
||||
### Translators
|
||||
|
||||
* Arabic @baro0k
|
||||
* Czech @pixelmade2
|
||||
@@ -27,8 +66,11 @@ https://www.transifex.com/mempool/mempool/dashboard/
|
||||
* Slovenian @thepkbadger
|
||||
* Finnish @bio_bitcoin
|
||||
* Swedish @softsimon_
|
||||
* Thai @Gusb3ll
|
||||
* Turkish @stackmore
|
||||
* Ukrainian @volbil
|
||||
* Vietnamese @bitcoin_vietnam
|
||||
* Chinese @wdljt
|
||||
* Russian @TonyCrusoe @Bitconan
|
||||
* Romanian @mirceavesa
|
||||
* Macedonian @SkechBoy
|
||||
|
||||
@@ -94,6 +94,10 @@
|
||||
"translation": "src/locale/messages.sv.xlf",
|
||||
"baseHref": "/sv/"
|
||||
},
|
||||
"th": {
|
||||
"translation": "src/locale/messages.th.xlf",
|
||||
"baseHref": "/th/"
|
||||
},
|
||||
"tr": {
|
||||
"translation": "src/locale/messages.tr.xlf",
|
||||
"baseHref": "/tr/"
|
||||
@@ -114,10 +118,18 @@
|
||||
"translation": "src/locale/messages.hu.xlf",
|
||||
"baseHref": "/hu/"
|
||||
},
|
||||
"mk": {
|
||||
"translation": "src/locale/messages.mk.xlf",
|
||||
"baseHref": "/mk/"
|
||||
},
|
||||
"zh": {
|
||||
"translation": "src/locale/messages.zh.xlf",
|
||||
"baseHref": "/zh/"
|
||||
},
|
||||
"ro": {
|
||||
"translation": "src/locale/messages.ro.xlf",
|
||||
"baseHref": "/ro/"
|
||||
},
|
||||
"ru": {
|
||||
"translation": "src/locale/messages.ru.xlf",
|
||||
"baseHref": "/ru/"
|
||||
@@ -137,7 +149,6 @@
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/resources",
|
||||
@@ -149,7 +160,13 @@
|
||||
],
|
||||
"scripts": [
|
||||
"generated-config.js"
|
||||
]
|
||||
],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -159,7 +176,14 @@
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": false
|
||||
},
|
||||
"fonts": true
|
||||
},
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
@@ -178,7 +202,8 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": ""
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
@@ -190,17 +215,17 @@
|
||||
"browserTarget": "mempool:build:production"
|
||||
},
|
||||
"local": {
|
||||
"proxyConfig": "proxy.conf.json",
|
||||
"proxyConfig": "proxy.conf.local.js",
|
||||
"verbose": true
|
||||
},
|
||||
"staging": {
|
||||
"proxyConfig": "proxy.stg.conf.json",
|
||||
"proxyConfig": "proxy.conf.js",
|
||||
"disableHostCheck": true,
|
||||
"host": "0.0.0.0",
|
||||
"verbose": true
|
||||
},
|
||||
"local-prod": {
|
||||
"proxyConfig": "proxy.prod.conf.json",
|
||||
"proxyConfig": "proxy.conf.js",
|
||||
"disableHostCheck": true,
|
||||
"host": "0.0.0.0",
|
||||
"verbose": false
|
||||
@@ -230,20 +255,6 @@
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"tsconfig.server.json",
|
||||
"cypress/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@cypress/schematic:cypress",
|
||||
"options": {
|
||||
@@ -262,7 +273,9 @@
|
||||
"options": {
|
||||
"outputPath": "dist/mempool/server",
|
||||
"main": "server.ts",
|
||||
"tsConfig": "tsconfig.server.json"
|
||||
"tsConfig": "tsconfig.server.json",
|
||||
"sourceMap": true,
|
||||
"optimization": false
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -277,7 +290,8 @@
|
||||
"localize": true,
|
||||
"optimization": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": ""
|
||||
},
|
||||
"serve-ssr": {
|
||||
"builder": "@nguniversal/builders:ssr-dev-server",
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
"retries": {
|
||||
"runMode": 3,
|
||||
"openMode": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"chromeWebSecurity": false
|
||||
}
|
||||
|
||||
52
frontend/cypress/fixtures/mainnet_rbf.json
Normal file
52
frontend/cypress/fixtures/mainnet_rbf.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"rbfTransaction": {
|
||||
"txid": "8913ec7ba0ede285dbd120e46f6d61a28f2903c10814a6f6c4f97d0edf3e1f46",
|
||||
"version": 2,
|
||||
"locktime": 632699,
|
||||
"vin": [
|
||||
{
|
||||
"txid": "02238126a63ea2669c5f378012180ef8b54402a949316f9b2f1352c51730a086",
|
||||
"vout": 0,
|
||||
"prevout": {
|
||||
"scriptpubkey": "a914f8e495456956c833e5e8c69b9a9dc041aa14c72f87",
|
||||
"scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 f8e495456956c833e5e8c69b9a9dc041aa14c72f OP_EQUAL",
|
||||
"scriptpubkey_type": "p2sh",
|
||||
"scriptpubkey_address": "3QP3LMD8veT5GtWV83Nosif2Bhr73857VB",
|
||||
"value": 25000000
|
||||
},
|
||||
"scriptsig": "22002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
|
||||
"scriptsig_asm": "OP_PUSHBYTES_34 002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
|
||||
"witness": [
|
||||
"",
|
||||
"3044022009e2d3a8e645f65bc89c8492cd9c08e6fb02609fd402214884a754a1970145340220575bb325429def59f3a3f1e22d9740a3feecbe97438ff3bb5796b2c46b3c477f01",
|
||||
"3044022039c34372882da8fc1c1243bd72b5e7e5e6870301ef56bdebb87bc647fb50f9b5022071a704ee77d742f78b10e45be675d4c45a5f31e884139e75c975144fde70e41701",
|
||||
"522102346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf70421021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b52ae"
|
||||
],
|
||||
"is_coinbase": false,
|
||||
"sequence": 4294967293,
|
||||
"inner_redeemscript_asm": "OP_0 OP_PUSHBYTES_32 43288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
|
||||
"inner_witnessscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 02346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf704 OP_PUSHBYTES_33 021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b OP_PUSHNUM_2 OP_CHECKMULTISIG"
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"scriptpubkey": "a914fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f87",
|
||||
"scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f OP_EQUAL",
|
||||
"scriptpubkey_type": "p2sh",
|
||||
"scriptpubkey_address": "3QnNmDhZS7toHA7bhhbTPBdtpLJoeecq5c",
|
||||
"value": 13986350
|
||||
},
|
||||
{
|
||||
"scriptpubkey": "76a914edc93d0446deec1c2d514f3a490f050096e74e0e88ac",
|
||||
"scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 edc93d0446deec1c2d514f3a490f050096e74e0e OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"scriptpubkey_type": "p2pkh",
|
||||
"scriptpubkey_address": "1NgJDkTUqJxxCAAZrrsC87kWag5kphrRtM",
|
||||
"value": 11000000
|
||||
}
|
||||
],
|
||||
"size": 372,
|
||||
"weight": 828,
|
||||
"fee": 1.5,
|
||||
"status": { "confirmed": false }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
describe('Bisq', () => {
|
||||
const baseModule = Cypress.env("BASE_MODULE");
|
||||
const basePath = (baseModule === 'bisq') ? '' : '/bisq';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('/sockjs-node/info*').as('socket');
|
||||
cy.intercept('/bisq/api/markets/hloc?market=btc_usd&interval=day').as('hloc');
|
||||
@@ -20,62 +23,65 @@ describe('Bisq', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/bisq');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
if (baseModule === 'mempool' || baseModule === 'bisq') {
|
||||
|
||||
it('loads the transactions screen', () => {
|
||||
cy.visit('/bisq');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.get('.table > tr').should('have.length', 50);
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit('/bisq');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait('@blocks');
|
||||
it('loads the transactions screen', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.get('.table > tr').should('have.length', 50);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait('@blocks');
|
||||
cy.get('tbody tr').should('have.length', 10);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the stats screen', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.wait('@stats');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.get('.section-header').should('have.length.at.least', 1);
|
||||
cy.get('.endpoint-container').should('have.length.at.least', 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks pagination with 5 pages (desktop)', () => {
|
||||
cy.viewport(760, 800);
|
||||
cy.visit(`${basePath}/blocks`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('tbody tr').should('have.length', 10);
|
||||
// 5 pages + 4 buttons = 9 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the stats screen', () => {
|
||||
cy.visit('/bisq');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.wait('@stats');
|
||||
it('shows blocks pagination with 3 pages (mobile)', () => {
|
||||
cy.viewport(669, 800);
|
||||
cy.visit(`${basePath}/blocks`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('tbody tr').should('have.length', 10);
|
||||
// 3 pages + 4 buttons = 7 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/bisq');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.get('.card').should('have.length.at.least', 1);
|
||||
cy.get('.card').first().click();
|
||||
cy.get('.card-body');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks pagination with 5 pages (desktop)', () => {
|
||||
cy.viewport(760, 800);
|
||||
cy.visit('/bisq/blocks');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('tbody tr').should('have.length', 10);
|
||||
// 5 pages + 4 buttons = 9 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
|
||||
});
|
||||
|
||||
it('shows blocks pagination with 3 pages (mobile)', () => {
|
||||
cy.viewport(669, 800);
|
||||
cy.visit('/bisq/blocks');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('tbody tr').should('have.length', 10);
|
||||
// 3 pages + 4 buttons = 7 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
|
||||
});
|
||||
|
||||
} else {
|
||||
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
describe('Liquid', () => {
|
||||
const baseModule = Cypress.env("BASE_MODULE");
|
||||
const basePath = (baseModule === 'liquid') ? '' : '/liquid';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('/liquid/api/block/**').as('block');
|
||||
cy.intercept('/liquid/api/blocks/').as('blocks');
|
||||
@@ -13,137 +16,186 @@ describe('Liquid', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/liquid');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
if (baseModule === 'mempool' || baseModule === 'liquid') {
|
||||
|
||||
it('loads the blocks page', () => {
|
||||
cy.visit('/liquid/blocks');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads a specific block page', () => {
|
||||
cy.visit('/liquid/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the graphs page', () => {
|
||||
cy.visit('/liquid/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the tv page - desktop', () => {
|
||||
cy.visit('/liquid');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the graphs page - mobile', () => {
|
||||
cy.visit('/liquid');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('assets', () => {
|
||||
it('shows the assets screen', () => {
|
||||
cy.visit('/liquid');
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.get('table tr').should('have.length', 5);
|
||||
cy.get('#mempool-block-0 > .blockLink').should('exist');
|
||||
});
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the blocks page', () => {
|
||||
cy.visit(`${basePath}/blocks`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads a specific block page', () => {
|
||||
cy.visit(`${basePath}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the graphs page', () => {
|
||||
cy.visit(`${basePath}/graphs`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the tv page - desktop', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows searching assets', () => {
|
||||
cy.visit('/liquid');
|
||||
it('loads the graphs page - mobile', () => {
|
||||
cy.visit(`${basePath}`)
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('renders unconfidential addresses correctly on mobile', () => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.visit(`${basePath}/address/ex1qqmmjdwrlg59c8q4l75sj6wedjx57tj5grt8pat`);
|
||||
cy.waitForSkeletonGone();
|
||||
//TODO: Add proper IDs for these selectors
|
||||
const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody';
|
||||
const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)';
|
||||
cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => {
|
||||
cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => {
|
||||
expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('peg in/peg out', () => {
|
||||
it('loads peg in addresses', () => {
|
||||
cy.visit(`${basePath}/tx/fe764f7bedfc2a37b29d9c8aef67d64a57d253a6b11c5a55555cfd5826483a58`);
|
||||
cy.waitForSkeletonGone();
|
||||
//TODO: Change to an element id so we don't assert on a string
|
||||
cy.get('#table-tx-vin').should('contain', 'Peg-in');
|
||||
cy.get('#table-tx-vin a').click().then(() => {
|
||||
cy.waitForSkeletonGone();
|
||||
if (baseModule === 'liquid') {
|
||||
cy.url().should('eq', 'https://mempool.space/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592');
|
||||
} else {
|
||||
//TODO: Use an environment variable to get the hostname
|
||||
cy.url().should('eq', 'http://localhost:4200/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('loads peg out addresses', () => {
|
||||
cy.visit(`${basePath}/tx/ecf6eba04ffb3946faa172343c87162df76f1a57b07b0d6dc6ad956b13376dc8`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vout a').first().click().then(() => {
|
||||
cy.waitForSkeletonGone();
|
||||
if (baseModule === 'liquid') {
|
||||
cy.url().should('eq', 'https://mempool.space/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR');
|
||||
} else {
|
||||
//TODO: Use an environment variable to get the hostname
|
||||
cy.url().should('eq', 'http://localhost:4200/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR');
|
||||
}
|
||||
//TODO: Add a custom class so we don't assert on a string
|
||||
cy.get('.badge').should('contain','Liquid Peg Out');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('assets', () => {
|
||||
it('shows the assets screen', () => {
|
||||
cy.visit(`${basePath}/assets`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('table tr').should('have.length.at.least', 5);
|
||||
});
|
||||
|
||||
it('allows searching assets', () => {
|
||||
cy.visit(`${basePath}/assets`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
|
||||
cy.get('table tr').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a specific asset ID', () => {
|
||||
cy.visit('/liquid');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
|
||||
cy.get('table tr td:nth-of-type(4) a').click();
|
||||
it('shows a specific asset ID', () => {
|
||||
cy.visit(`${basePath}/assets`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.container-xl input').click().type('Liquid AUD').then(() => {
|
||||
cy.get('table tr td:nth-of-type(1) a').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows a specific asset issuance TX', () => {
|
||||
cy.visit('/liquid');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
|
||||
cy.get('table tr td:nth-of-type(5) a').click();
|
||||
});
|
||||
|
||||
describe('unblinded TX', () => {
|
||||
|
||||
it('should not show an unblinding error message for regular txs', () => {
|
||||
cy.visit(`${basePath}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.error-unblinded' ).should('not.exist');
|
||||
});
|
||||
|
||||
it('show unblinded TX', () => {
|
||||
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vin tr').should('have.class', 'assetBox');
|
||||
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
|
||||
});
|
||||
|
||||
it('show empty unblinded TX', () => {
|
||||
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vin tr').should('have.class', '');
|
||||
cy.get('#table-tx-vout tr').should('have.class', '');
|
||||
});
|
||||
|
||||
it('show invalid unblinded TX hex', () => {
|
||||
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vin tr').should('have.class', '');
|
||||
cy.get('#table-tx-vout tr').should('have.class', '');
|
||||
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data (invalid hex)');
|
||||
});
|
||||
|
||||
it('show first unblinded vout', () => {
|
||||
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox');
|
||||
});
|
||||
|
||||
it('show second unblinded vout', () => {
|
||||
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
|
||||
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
|
||||
});
|
||||
|
||||
it('show invalid error unblinded TX', () => {
|
||||
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
|
||||
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.');
|
||||
});
|
||||
|
||||
it('shows asset peg in/out and burn transactions', () => {
|
||||
cy.visit(`${basePath}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vout tr').not('.assetBox');
|
||||
cy.get('#table-tx-vin tr').not('.assetBox');
|
||||
});
|
||||
|
||||
it('prevents regressing issue #644', () => {
|
||||
cy.visit(`${basePath}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('unblinded TX', () => {
|
||||
it('show unblinded TX', () => {
|
||||
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vin tr').should('have.class', 'assetBox');
|
||||
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
|
||||
});
|
||||
|
||||
it('show empty unblinded TX', () => {
|
||||
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vin tr').should('have.class', '');
|
||||
cy.get('#table-tx-vout tr').should('have.class', '');
|
||||
});
|
||||
|
||||
it('show invalid unblinded TX hex', () => {
|
||||
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vin tr').should('have.class', '');
|
||||
cy.get('#table-tx-vout tr').should('have.class', '');
|
||||
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data (invalid hex)');
|
||||
});
|
||||
|
||||
it('show first unblinded vout', () => {
|
||||
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox');
|
||||
});
|
||||
|
||||
it('show second unblinded vout', () => {
|
||||
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a');
|
||||
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
|
||||
});
|
||||
|
||||
it('show invalid error unblinded TX', () => {
|
||||
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
|
||||
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.');
|
||||
});
|
||||
|
||||
it('shows asset peg in/out and burn transactions', () => {
|
||||
cy.visit('/liquid/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#table-tx-vout tr').not('.assetBox');
|
||||
cy.get('#table-tx-vin tr').not('.assetBox');
|
||||
});
|
||||
|
||||
it('prevents regressing issue #644', () => {
|
||||
cy.visit('/liquid/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
import { emitMempoolInfo, dropWebSocket } from "../../support/websocket";
|
||||
|
||||
const baseModule = Cypress.env("BASE_MODULE");
|
||||
|
||||
|
||||
//Credit: https://github.com/bahmutov/cypress-examples/blob/6cedb17f83a3bb03ded13cf1d6a3f0656ca2cdf5/docs/recipes/overlapping-elements.md
|
||||
|
||||
/**
|
||||
* Returns true if two DOM rectangles are overlapping
|
||||
* @param {DOMRect} rect1 the bounding client rectangle of the first element
|
||||
* @param {DOMRect} rect2 the bounding client rectangle of the second element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const areOverlapping = (rect1, rect2) => {
|
||||
// if one rectangle is on the left side of the other
|
||||
if (rect1.right < rect2.left || rect2.right < rect1.left) {
|
||||
return false
|
||||
}
|
||||
|
||||
// if one rectangle is above the other
|
||||
if (rect1.bottom < rect2.top || rect2.bottom < rect1.top) {
|
||||
return false
|
||||
}
|
||||
|
||||
// the rectangles must overlap
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bounding rectangle of the first DOM
|
||||
* element in the given jQuery object.
|
||||
*/
|
||||
const getRectangle = ($el) => $el[0].getBoundingClientRect();
|
||||
|
||||
describe('Mainnet', () => {
|
||||
beforeEach(() => {
|
||||
//cy.intercept('/sockjs-node/info*').as('socket');
|
||||
@@ -9,190 +41,496 @@ describe('Mainnet', () => {
|
||||
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
|
||||
cy.intercept('/resources/pools.json').as('pools');
|
||||
|
||||
// Search Auto Complete
|
||||
cy.intercept('/api/address-prefix/1wiz').as('search-1wiz');
|
||||
cy.intercept('/api/address-prefix/1wizS').as('search-1wizS');
|
||||
cy.intercept('/api/address-prefix/1wizSA').as('search-1wizSA');
|
||||
|
||||
Cypress.Commands.add('waitForBlockData', () => {
|
||||
cy.wait('@tx-outspends');
|
||||
cy.wait('@pools');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the status screen', () => {
|
||||
cy.visit('/status');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('[id^="bitcoin-block-"]').should('have.length', 8);
|
||||
cy.get('.footer').should('be.visible');
|
||||
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
|
||||
expect(text).to.match(/Tx vBytes per second:.* vB\/s/);
|
||||
});
|
||||
cy.get('.row > :nth-child(2)').invoke('text').then((text) => {
|
||||
expect(text).to.match(/Unconfirmed:(.*)/);
|
||||
});
|
||||
cy.get('.row > :nth-child(3)').invoke('text').then((text) => {
|
||||
expect(text).to.match(/Mempool size:(.*) (kB|MB) \((\d+) (block|blocks)\)/);
|
||||
});
|
||||
});
|
||||
if (baseModule === 'mempool') {
|
||||
|
||||
it('loads dashboard, drop websocket and reconnect', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit('/');
|
||||
cy.get('.badge').should('not.exist');
|
||||
dropWebSocket();
|
||||
cy.get('.badge').should('be.visible');
|
||||
cy.get('.badge', {timeout: 25000}).should('not.exist');
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
loaded: true
|
||||
}
|
||||
});
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads skeleton when changes between networks', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
|
||||
cy.changeNetwork("testnet");
|
||||
cy.changeNetwork("signet");
|
||||
cy.changeNetwork("liquid");
|
||||
cy.changeNetwork("mainnet");
|
||||
cy.changeNetwork("bisq");
|
||||
});
|
||||
|
||||
it('loads the dashboard with the skeleton blocks', () => {
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit("/");
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-1').should('be.visible');
|
||||
cy.get('#mempool-block-2').should('be.visible');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
loaded: true
|
||||
}
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#mempool-block-0 > .blockLink').should('exist');
|
||||
});
|
||||
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.waitForPageIdle();
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the graphs screen', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.get('.chart-holder');
|
||||
cy.get('.blockchain-wrapper').should('be.visible');
|
||||
it('loads the status screen', () => {
|
||||
cy.visit('/status');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.visit('/tv');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.chart-holder');
|
||||
cy.get('.blockchain-wrapper').should('be.visible');
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
it('shows empty blocks properly', () => {
|
||||
cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||
});
|
||||
|
||||
it('expands and collapses the block details', () => {
|
||||
cy.visit('/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('be.visible');
|
||||
cy.get('[id^="bitcoin-block-"]').should('have.length', 8);
|
||||
cy.get('.footer').should('be.visible');
|
||||
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
|
||||
expect(text).to.match(/Tx vBytes per second:.* vB\/s/);
|
||||
});
|
||||
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('not.be.visible');
|
||||
cy.get('.row > :nth-child(2)').invoke('text').then((text) => {
|
||||
expect(text).to.match(/Unconfirmed:(.*)/);
|
||||
});
|
||||
cy.get('.row > :nth-child(3)').invoke('text').then((text) => {
|
||||
expect(text).to.match(/Mempool size:(.*) (kB|MB) \((\d+) (block|blocks)\)/);
|
||||
});
|
||||
});
|
||||
it('shows blocks with no pagination', () => {
|
||||
cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('.block-tx-title h2').invoke('text').should('equal', '19 transactions');
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 5);
|
||||
|
||||
it('loads dashboard, drop websocket and reconnect', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit('/');
|
||||
cy.get('.badge').should('not.exist');
|
||||
dropWebSocket();
|
||||
cy.get('.badge').should('be.visible');
|
||||
cy.get('.badge', {timeout: 25000}).should('not.exist');
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
command: 'init'
|
||||
}
|
||||
});
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('supports pagination on the block screen', () => {
|
||||
// 41 txs
|
||||
cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8');
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.pagination-container a').invoke('text').then((text1) => {
|
||||
cy.get('.active + li').first().click().then(() => {
|
||||
});
|
||||
|
||||
it('check op_return tx tooltip', () => {
|
||||
cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('onmouseover');
|
||||
cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('mouseenter');
|
||||
cy.get('.tooltip-inner').should('be.visible');
|
||||
});
|
||||
|
||||
it('check op_return coinbase tooltip', () => {
|
||||
cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('div > a > .badge').first().trigger('onmouseover');
|
||||
cy.get('div > a > .badge').first().trigger('mouseenter');
|
||||
cy.get('.tooltip-inner').should('be.visible');
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('allows searching for partial Bitcoin addresses', () => {
|
||||
cy.visit('/');
|
||||
cy.get('.search-box-container > .form-control').type('1wiz').then(() => {
|
||||
cy.wait('@search-1wiz');
|
||||
cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 10);
|
||||
});
|
||||
|
||||
cy.get('.search-box-container > .form-control').type('S').then(() => {
|
||||
cy.wait('@search-1wizS');
|
||||
cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 5);
|
||||
});
|
||||
|
||||
cy.get('.search-box-container > .form-control').type('A').then(() => {
|
||||
cy.wait('@search-1wizSA');
|
||||
cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1)
|
||||
});
|
||||
|
||||
cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => {
|
||||
cy.url().should('include', '/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address');
|
||||
});
|
||||
});
|
||||
|
||||
['BC1PQYQSZQ', 'bc1PqYqSzQ'].forEach((searchTerm) => {
|
||||
it(`allows searching for partial case insensitive bech32m addresses: ${searchTerm}`, () => {
|
||||
cy.visit('/');
|
||||
cy.get('.search-box-container > .form-control').type(searchTerm).then(() => {
|
||||
cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1);
|
||||
cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => {
|
||||
cy.url().should('include', '/address/bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
['BC1Q000375VXCU', 'bC1q000375vXcU'].forEach((searchTerm) => {
|
||||
it(`allows searching for partial case insensitive bech32 addresses: ${searchTerm}`, () => {
|
||||
cy.visit('/');
|
||||
cy.get('.search-box-container > .form-control').type(searchTerm).then(() => {
|
||||
cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1);
|
||||
cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => {
|
||||
cy.url().should('include', '/address/bc1q000375vxcuf5v04lmwy22vy2thvhqkxghgq7dy');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('blocks navigation', () => {
|
||||
|
||||
describe('keyboard events', () => {
|
||||
it('loads first blockchain blocks visible and keypress arrow right', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.blockchain-blocks-0 > a').click().then(() => {
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.waitForPageIdle();
|
||||
cy.document().right();
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads first blockchain blocks visible and keypress arrow left', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.blockchain-blocks-0 > a').click().then(() => {
|
||||
cy.waitForPageIdle();
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.document().left();
|
||||
cy.get('.title-block h1').invoke('text').should('equal', 'Next block');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads last blockchain blocks and keypress arrow right', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.blockchain-blocks-4 > a').click().then(() => {
|
||||
cy.waitForPageIdle();
|
||||
|
||||
// block 6
|
||||
cy.document().right();
|
||||
cy.wait(5000);
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
|
||||
// block 7
|
||||
cy.document().right();
|
||||
cy.wait(5000);
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
|
||||
// block 8 - last visible block
|
||||
cy.document().right();
|
||||
cy.wait(5000);
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
|
||||
// block 9 - not visible at the blochchain blocks visible block
|
||||
cy.document().right();
|
||||
cy.wait(5000);
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('loads genesis block and keypress arrow right', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
|
||||
expect(text1).not.to.eq(text2);
|
||||
|
||||
cy.document().right();
|
||||
cy.wait(5000);
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads genesis block and keypress arrow left', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
|
||||
cy.document().left();
|
||||
cy.wait(5000);
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
});
|
||||
});
|
||||
describe('mouse events', () => {
|
||||
it('loads first blockchain blocks visible and click on the arrow right', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.blockchain-blocks-0 > a').click().then(() => {
|
||||
cy.waitForPageIdle();
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => {
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('loads genesis block and click on the arrow left', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => {
|
||||
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks pagination with 5 pages (desktop)', () => {
|
||||
cy.viewport(760, 800);
|
||||
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
});
|
||||
|
||||
// 5 pages + 4 buttons = 9 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
|
||||
|
||||
it('loads skeleton when changes between networks', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
|
||||
cy.changeNetwork("testnet");
|
||||
cy.changeNetwork("signet");
|
||||
cy.changeNetwork("liquid");
|
||||
cy.changeNetwork("mainnet");
|
||||
cy.changeNetwork("bisq");
|
||||
});
|
||||
|
||||
it('shows blocks pagination with 3 pages (mobile)', () => {
|
||||
cy.viewport(669, 800);
|
||||
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
|
||||
cy.waitForSkeletonGone();
|
||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit("/");
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-1').should('be.visible');
|
||||
cy.get('#mempool-block-2').should('be.visible');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
command: 'init'
|
||||
}
|
||||
});
|
||||
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.waitForPageIdle();
|
||||
});
|
||||
|
||||
// 3 pages + 4 buttons = 7 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the graphs screen', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('graphs page', () => {
|
||||
it('check buttons - mobile', () => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.visit('/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.small-buttons > :nth-child(2)').should('be.visible');
|
||||
cy.get('#dropdownFees').should('be.visible');
|
||||
cy.get('.btn-group').should('be.visible');
|
||||
});
|
||||
it('check buttons - tablet', () => {
|
||||
cy.viewport('ipad-2');
|
||||
cy.visit('/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.small-buttons > :nth-child(2)').should('be.visible');
|
||||
cy.get('#dropdownFees').should('be.visible');
|
||||
cy.get('.btn-group').should('be.visible');
|
||||
});
|
||||
it('check buttons - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/graphs');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.small-buttons > :nth-child(2)').should('be.visible');
|
||||
cy.get('#dropdownFees').should('be.visible');
|
||||
cy.get('.btn-group').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.get('.chart-holder');
|
||||
cy.get('.blockchain-wrapper').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.visit('/tv');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.chart-holder');
|
||||
cy.get('.blockchain-wrapper').should('not.visible');
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
it('shows empty blocks properly', () => {
|
||||
cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||
});
|
||||
|
||||
it('expands and collapses the block details', () => {
|
||||
cy.visit('/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('be.visible');
|
||||
});
|
||||
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
it('shows blocks with no pagination', () => {
|
||||
cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('.block-tx-title h2').invoke('text').should('equal', '19 transactions');
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 5);
|
||||
});
|
||||
|
||||
it('supports pagination on the block screen', () => {
|
||||
// 41 txs
|
||||
cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.pagination-container a').invoke('text').then((text1) => {
|
||||
cy.get('.active + li').first().click().then(() => {
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
|
||||
expect(text1).not.to.eq(text2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks pagination with 5 pages (desktop)', () => {
|
||||
cy.viewport(760, 800);
|
||||
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
});
|
||||
|
||||
// 5 pages + 4 buttons = 9 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
|
||||
});
|
||||
|
||||
it('shows blocks pagination with 3 pages (mobile)', () => {
|
||||
cy.viewport(669, 800);
|
||||
cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => {
|
||||
cy.waitForSkeletonGone();
|
||||
cy.waitForPageIdle();
|
||||
});
|
||||
|
||||
// 3 pages + 4 buttons = 7 buttons
|
||||
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RBF transactions', () => {
|
||||
it('shows RBF transactions properly (mobile)', () => {
|
||||
cy.viewport('iphone-xr');
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
|
||||
|
||||
cy.waitForSkeletonGone();
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
command: 'init'
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('#mempool-block-0');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
command: 'rbfTransaction'
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('.alert-mempool').should('be.visible');
|
||||
cy.get('.alert-mempool').invoke('css', 'width').then((alertWidth) => {
|
||||
cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth);
|
||||
});
|
||||
|
||||
cy.get('.btn-success').then(getRectangle).then((rectA) => {
|
||||
cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
|
||||
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows RBF transactions properly (desktop)', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
|
||||
|
||||
cy.waitForSkeletonGone();
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
command: 'init'
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('#mempool-block-0');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
command: 'rbfTransaction'
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('.alert-mempool').should('be.visible');
|
||||
|
||||
const alertLocator = '.alert-mempool';
|
||||
const tableLocator = '.container-xl > :nth-child(3)';
|
||||
|
||||
cy.get(tableLocator).invoke('css', 'width').then((firstWidth) => {
|
||||
cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth);
|
||||
});
|
||||
|
||||
cy.get('.btn-success').then(getRectangle).then((rectA) => {
|
||||
cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
|
||||
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { emitMempoolInfo } from "../../support/websocket";
|
||||
|
||||
const baseModule = Cypress.env("BASE_MODULE");
|
||||
|
||||
describe('Signet', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('/api/block-height/*').as('block-height');
|
||||
@@ -8,119 +10,130 @@ describe('Signet', () => {
|
||||
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
|
||||
});
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the dashboard with the skeleton blocks', () => {
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit("/signet");
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-1').should('be.visible');
|
||||
cy.get('#mempool-block-2').should('be.visible');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
"network": "signet"
|
||||
}
|
||||
if (baseModule === 'mempool') {
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit('/signet');
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the graphs screen', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tv mode', () => {
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.get('.chart-holder').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.viewport('iphone-8');
|
||||
cy.get('.chart-holder').should('be.visible');
|
||||
//TODO: Remove comment when the bug is fixed
|
||||
//cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
it('shows empty blocks properly', () => {
|
||||
cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||
});
|
||||
|
||||
it('expands and collapses the block details', () => {
|
||||
cy.visit('/signet/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('be.visible');
|
||||
});
|
||||
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks with no pagination', () => {
|
||||
cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '13 transactions');
|
||||
cy.get('ul.pagination').first().children().should('have.length', 5);
|
||||
});
|
||||
|
||||
it('supports pagination on the block screen', () => {
|
||||
// 43 txs
|
||||
cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
|
||||
cy.get('.active + li').first().click().then(() => {
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
|
||||
expect(text1).not.to.eq(text2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('#mempool-block-0 > .blockLink').should('exist');
|
||||
});
|
||||
|
||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit("/signet");
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-1').should('be.visible');
|
||||
cy.get('#mempool-block-2').should('be.visible');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
"network": "signet"
|
||||
}
|
||||
});
|
||||
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the graphs screen', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tv mode', () => {
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.get('.chart-holder').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.viewport('iphone-8');
|
||||
cy.get('.chart-holder').should('be.visible');
|
||||
//TODO: Remove comment when the bug is fixed
|
||||
//cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
it('shows empty blocks properly', () => {
|
||||
cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||
});
|
||||
|
||||
it('expands and collapses the block details', () => {
|
||||
cy.visit('/signet/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('be.visible');
|
||||
});
|
||||
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks with no pagination', () => {
|
||||
cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '13 transactions');
|
||||
cy.get('ul.pagination').first().children().should('have.length', 5);
|
||||
});
|
||||
|
||||
it('supports pagination on the block screen', () => {
|
||||
// 43 txs
|
||||
cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
|
||||
cy.get('.active + li').first().click().then(() => {
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
|
||||
expect(text1).not.to.eq(text2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { emitMempoolInfo } from "../../support/websocket";
|
||||
import { confirmAddress, emitMempoolInfo, sendWsMock, showNewTx, startTrackingAddress } from "../../support/websocket";
|
||||
|
||||
const baseModule = Cypress.env("BASE_MODULE");
|
||||
|
||||
describe('Testnet', () => {
|
||||
beforeEach(() => {
|
||||
@@ -8,115 +10,127 @@ describe('Testnet', () => {
|
||||
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
|
||||
});
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
if (baseModule === 'mempool') {
|
||||
|
||||
it('loads the dashboard with the skeleton blocks', () => {
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit("/signet");
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-1').should('be.visible');
|
||||
cy.get('#mempool-block-2').should('be.visible');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
loaded: true
|
||||
}
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit('/testnet');
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the graphs screen', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tv mode', () => {
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
it('shows empty blocks properly', () => {
|
||||
cy.visit('/testnet/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||
});
|
||||
|
||||
it('expands and collapses the block details', () => {
|
||||
cy.visit('/testnet/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('be.visible');
|
||||
});
|
||||
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks with no pagination', () => {
|
||||
cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '11 transactions');
|
||||
cy.get('ul.pagination').first().children().should('have.length', 5);
|
||||
});
|
||||
|
||||
it('supports pagination on the block screen', () => {
|
||||
// 48 txs
|
||||
cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
|
||||
cy.get('.active + li').first().click().then(() => {
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
|
||||
expect(text1).not.to.eq(text2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
cy.get('#mempool-block-0 > .blockLink').should('exist');
|
||||
});
|
||||
|
||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit("/testnet");
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-0').should('be.visible');
|
||||
cy.get('#mempool-block-1').should('be.visible');
|
||||
cy.get('#mempool-block-2').should('be.visible');
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
loaded: true
|
||||
}
|
||||
});
|
||||
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
|
||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the graphs screen', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tv mode', () => {
|
||||
it('loads the tv screen - desktop', () => {
|
||||
cy.viewport('macbook-16');
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('loads the tv screen - mobile', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.viewport('iphone-6');
|
||||
cy.wait(1000);
|
||||
cy.get('.tv-only').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
it('shows empty blocks properly', () => {
|
||||
cy.visit('/testnet/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||
});
|
||||
|
||||
it('expands and collapses the block details', () => {
|
||||
cy.visit('/testnet/block/0');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('be.visible');
|
||||
});
|
||||
|
||||
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||
cy.get('#details').should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows blocks with no pagination', () => {
|
||||
cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('h2').invoke('text').should('equal', '11 transactions');
|
||||
cy.get('ul.pagination').first().children().should('have.length', 5);
|
||||
});
|
||||
|
||||
it('supports pagination on the block screen', () => {
|
||||
// 48 txs
|
||||
cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
|
||||
cy.get('.active + li').first().click().then(() => {
|
||||
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
|
||||
expect(text1).not.to.eq(text2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
module.exports = (on, config) => {}
|
||||
const fs = require('fs');
|
||||
|
||||
const CONFIG_FILE = 'mempool-frontend-config.json';
|
||||
|
||||
module.exports = (on, config) => {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
let contents = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||
config.env.BASE_MODULE = contents.BASE_MODULE ? contents.BASE_MODULE : 'mempool';
|
||||
} else {
|
||||
config.env.BASE_MODULE = 'mempool';
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -42,14 +42,24 @@
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
'use strict'
|
||||
|
||||
import 'cypress-wait-until';
|
||||
import { PageIdleDetector } from './PageIdleDetector';
|
||||
import { mockWebSocket } from './websocket';
|
||||
|
||||
/* global Cypress */
|
||||
const codes = {
|
||||
ArrowLeft: 37,
|
||||
ArrowUp: 38,
|
||||
ArrowRight: 39,
|
||||
ArrowDown: 40
|
||||
}
|
||||
|
||||
Cypress.Commands.add('waitForSkeletonGone', () => {
|
||||
cy.waitUntil(() => {
|
||||
return Cypress.$('.skeleton-loader').length === 0;
|
||||
}, { verbose: true, description: "waitForSkeletonGone", errorMsg: "skeleton loaders never went away", timeout: 7000, interval: 50});
|
||||
}, { verbose: true, description: "waitForSkeletonGone", errorMsg: "skeleton loaders never went away", timeout: 15000, interval: 50});
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
@@ -75,3 +85,63 @@ Cypress.Commands.add('changeNetwork', (network: "testnet"|"signet"|"liquid"|"bis
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/bahmutov/cypress-arrows/blob/8f0303842a343550fbeaf01528d01d1ff213b70c/src/index.js
|
||||
function keydownCommand ($el, key) {
|
||||
const message = `sending the "${key}" keydown event`
|
||||
const log = Cypress.log({
|
||||
name: `keydown: ${key}`,
|
||||
message: message,
|
||||
consoleProps: function () {
|
||||
return {
|
||||
Subject: $el
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const e = $el.createEvent('KeyboardEvent')
|
||||
|
||||
Object.defineProperty(e, 'key', {
|
||||
get: function () {
|
||||
return key
|
||||
}
|
||||
})
|
||||
|
||||
Object.defineProperty(e, 'keyCode', {
|
||||
get: function () {
|
||||
return this.keyCodeVal
|
||||
}
|
||||
})
|
||||
Object.defineProperty(e, 'which', {
|
||||
get: function () {
|
||||
return this.keyCodeVal
|
||||
}
|
||||
})
|
||||
var metaKey = false
|
||||
|
||||
Object.defineProperty(e, 'metaKey', {
|
||||
get: function () {
|
||||
return metaKey
|
||||
}
|
||||
})
|
||||
|
||||
Object.defineProperty(e, 'shiftKey', {
|
||||
get: function () {
|
||||
return false
|
||||
}
|
||||
})
|
||||
e.keyCodeVal = codes[key]
|
||||
|
||||
e.initKeyboardEvent('keydown', true, true,
|
||||
$el.defaultView, false, false, false, false, e.keyCodeVal, e.keyCodeVal)
|
||||
|
||||
$el.dispatchEvent(e)
|
||||
log.snapshot().end()
|
||||
return $el
|
||||
}
|
||||
|
||||
Cypress.Commands.add('keydown', { prevSubject: "dom" }, keydownCommand)
|
||||
Cypress.Commands.add('left', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowLeft'))
|
||||
Cypress.Commands.add('right', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowRight'))
|
||||
Cypress.Commands.add('up', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowUp'))
|
||||
Cypress.Commands.add('down', { prevSubject: "dom" }, $el => keydownCommand($el, 'ArrowDown'))
|
||||
@@ -31,19 +31,19 @@ export const mockWebSocket = () => {
|
||||
cy.on('window:before:load', (win) => {
|
||||
const winWebSocket = win.WebSocket;
|
||||
cy.stub(win, 'WebSocket').callsFake((url) => {
|
||||
console.log(url);
|
||||
console.log(url);
|
||||
if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0)) {
|
||||
const { server, websocket } = createMock(url);
|
||||
|
||||
win.mockServer = server;
|
||||
win.mockServer.on('connection', (socket) => {
|
||||
win.mockSocket = socket;
|
||||
win.mockSocket.send('{"action":"init"}');
|
||||
win.mockSocket.send('{"action":"init"}');
|
||||
});
|
||||
|
||||
win.mockServer.on('message', (message) => {
|
||||
console.log(message);
|
||||
});
|
||||
win.mockServer.on('message', (message) => {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
return websocket;
|
||||
} else {
|
||||
@@ -68,7 +68,13 @@ export const emitMempoolInfo = ({
|
||||
//TODO: Use network specific mocks
|
||||
case "signet":
|
||||
case "testnet":
|
||||
case "mainnet":
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (params.command) {
|
||||
case "init": {
|
||||
win.mockSocket.send('{"action":"init"}');
|
||||
win.mockSocket.send('{"action":"want","data":["blocks","stats","mempool-blocks","live-2h-chart"]}');
|
||||
win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}');
|
||||
@@ -78,6 +84,16 @@ export const emitMempoolInfo = ({
|
||||
cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'ascii').then((fixture) => {
|
||||
win.mockSocket.send(JSON.stringify(fixture));
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "rbfTransaction": {
|
||||
cy.readFile('cypress/fixtures/mainnet_rbf.json', 'ascii').then((fixture) => {
|
||||
win.mockSocket.send(JSON.stringify(fixture));
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
cy.waitForSkeletonGone();
|
||||
@@ -89,4 +105,4 @@ export const dropWebSocket = (() => {
|
||||
win.mockServer.simulate("error");
|
||||
});
|
||||
return cy.wait(500);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,16 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
const indexFilePath = configContent.BASE_MODULE ? 'src/index.' + configContent.BASE_MODULE + '.html' : 'src/index.mempool.html';
|
||||
|
||||
try {
|
||||
fs.copyFileSync(indexFilePath, 'src/index.html');
|
||||
console.log('Copied ' + indexFilePath + ' to src/index.html');
|
||||
} catch (e) {
|
||||
console.log('Error copying the index file');
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
try {
|
||||
const packageJson = fs.readFileSync('package.json');
|
||||
packetJsonVersion = JSON.parse(packageJson).version;
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
"TESTNET_ENABLED": false,
|
||||
"SIGNET_ENABLED": false,
|
||||
"LIQUID_ENABLED": false,
|
||||
"LIQUID_TESTNET_ENABLED": false,
|
||||
"BISQ_ENABLED": false,
|
||||
"BISQ_SEPARATE_BACKEND": false,
|
||||
"ITEMS_PER_PAGE": 10,
|
||||
"KEEP_BLOCKS_AMOUNT": 8,
|
||||
"NGINX_PROTOCOL": "http",
|
||||
"NGINX_HOSTNAME": "127.0.0.1",
|
||||
"NGINX_PORT": "80"
|
||||
"NGINX_PORT": "80",
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||
"BLOCK_WEIGHT_UNITS": 4000000,
|
||||
"BASE_MODULE": "mempool"
|
||||
}
|
||||
|
||||
25307
frontend/package-lock.json
generated
25307
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mempool-frontend",
|
||||
"version": "2.2.1",
|
||||
"version": "2.3.0-dev",
|
||||
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"homepage": "https://mempool.space",
|
||||
@@ -22,7 +22,7 @@
|
||||
"scripts": {
|
||||
"ng": "./node_modules/@angular/cli/bin/ng",
|
||||
"tsc": "./node_modules/typescript/bin/tsc",
|
||||
"i18n-extract-from-source": "./node_modules/@angular/cli/bin/ng extract-i18n --ivy --out-file ./src/locale/messages.xlf",
|
||||
"i18n-extract-from-source": "./node_modules/@angular/cli/bin/ng extract-i18n --out-file ./src/locale/messages.xlf",
|
||||
"i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1 --force",
|
||||
"serve": "npm run generate-config && ng serve -c local",
|
||||
"serve:stg": "npm run generate-config && ng serve -c staging",
|
||||
@@ -30,16 +30,21 @@
|
||||
"start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local",
|
||||
"start:stg": "npm run generate-config && npm run sync-assets-dev && ng serve -c staging",
|
||||
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-prod",
|
||||
"build": "npm run generate-config && ng build --prod --localize && npm run sync-assets && npm run build-mempool.js",
|
||||
"build": "npm run generate-config && ng build --configuration production --localize && npm run sync-assets && npm run build-mempool.js",
|
||||
"sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
|
||||
"sync-assets-dev": "node sync-assets.js dev",
|
||||
"generate-config": "node generate-config.js",
|
||||
"build-mempool.js": "tsc | browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
|
||||
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
|
||||
"build-mempool-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
|
||||
"build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
|
||||
"build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"e2e": "npm run generate-config && ng e2e",
|
||||
"e2e:ci": "npm run cypress:run:ci",
|
||||
"config:defaults": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config",
|
||||
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool && npm run generate-config",
|
||||
"config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid && npm run generate-config",
|
||||
"config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq && npm run generate-config",
|
||||
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
|
||||
"serve:ssr": "node server.run.js",
|
||||
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",
|
||||
@@ -51,34 +56,38 @@
|
||||
"cypress:run:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "~11.2.8",
|
||||
"@angular/common": "~11.2.8",
|
||||
"@angular/compiler": "~11.2.8",
|
||||
"@angular/core": "~11.2.8",
|
||||
"@angular/forms": "~11.2.8",
|
||||
"@angular/localize": "^11.2.8",
|
||||
"@angular/platform-browser": "~11.2.8",
|
||||
"@angular/platform-browser-dynamic": "~11.2.8",
|
||||
"@angular/platform-server": "~11.2.8",
|
||||
"@angular/router": "~11.2.8",
|
||||
"@angular-devkit/build-angular": "^13.0.4",
|
||||
"@angular/animations": "~13.0.3",
|
||||
"@angular/cli": "~13.0.4",
|
||||
"@angular/common": "~13.0.3",
|
||||
"@angular/compiler": "~13.0.3",
|
||||
"@angular/core": "~13.0.3",
|
||||
"@angular/forms": "~13.0.3",
|
||||
"@angular/localize": "^13.0.3",
|
||||
"@angular/platform-browser": "~13.0.3",
|
||||
"@angular/platform-browser-dynamic": "~13.0.3",
|
||||
"@angular/platform-server": "~13.0.3",
|
||||
"@angular/router": "~13.0.3",
|
||||
"@fortawesome/angular-fontawesome": "^0.8.2",
|
||||
"@fortawesome/fontawesome-common-types": "^0.2.35",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@mempool/chartist": "^0.11.4",
|
||||
"@mempool/mempool.js": "^2.2.4",
|
||||
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
||||
"@juggle/resize-observer": "^3.3.1",
|
||||
"@mempool/mempool.js": "2.3.0-dev1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
|
||||
"@nguniversal/express-engine": "11.2.1",
|
||||
"@types/qrcode": "^1.3.4",
|
||||
"@types/qrcode": "1.4.1",
|
||||
"bootstrap": "4.5.0",
|
||||
"browserify": "^17.0.0",
|
||||
"clipboard": "^2.0.4",
|
||||
"domino": "^2.1.6",
|
||||
"echarts": "^5.1.2",
|
||||
"express": "^4.17.1",
|
||||
"lightweight-charts": "^3.3.0",
|
||||
"ngx-bootrap-multiselect": "^2.0.0",
|
||||
"ngx-echarts": "^7.0.1",
|
||||
"ngx-infinite-scroll": "^10.0.1",
|
||||
"qrcode": "^1.4.4",
|
||||
"qrcode": "1.5.0",
|
||||
"rxjs": "^6.6.7",
|
||||
"tinyify": "^3.0.0",
|
||||
"tlite": "^0.1.9",
|
||||
@@ -86,10 +95,8 @@
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.1102.7",
|
||||
"@angular/cli": "~11.2.7",
|
||||
"@angular/compiler-cli": "~11.2.8",
|
||||
"@angular/language-service": "~11.2.8",
|
||||
"@angular/compiler-cli": "~13.0.3",
|
||||
"@angular/language-service": "~13.0.3",
|
||||
"@nguniversal/builders": "^11.2.1",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/jasmine": "~3.6.0",
|
||||
@@ -99,21 +106,21 @@
|
||||
"http-proxy-middleware": "^1.0.5",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~6.1.0",
|
||||
"karma": "~6.3.4",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.0.3",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "~4.1.5"
|
||||
"typescript": "~4.4.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "^1.3.0",
|
||||
"cypress": "^7.7.0",
|
||||
"cypress-fail-on-console-error": "^2.1.0",
|
||||
"cypress": "^9.1.1",
|
||||
"cypress-fail-on-console-error": "^2.1.3",
|
||||
"cypress-wait-until": "^1.7.1",
|
||||
"mock-socket": "^9.0.3",
|
||||
"start-server-and-test": "^1.12.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
90
frontend/proxy.conf.js
Normal file
90
frontend/proxy.conf.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const fs = require('fs');
|
||||
|
||||
let PROXY_CONFIG;
|
||||
let configContent;
|
||||
|
||||
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||
|
||||
try {
|
||||
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
|
||||
configContent = JSON.parse(rawConfig);
|
||||
console.log(`${CONFIG_FILE_NAME} file found, using provided config`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.code !== 'ENOENT') {
|
||||
throw new Error(e);
|
||||
} else {
|
||||
console.log(`${CONFIG_FILE_NAME} file not found, using default config`);
|
||||
}
|
||||
}
|
||||
|
||||
PROXY_CONFIG = [
|
||||
{
|
||||
context: ['*',
|
||||
'/api/**', '!/api/v1/ws',
|
||||
'!/bisq', '!/bisq/**', '!/bisq/',
|
||||
'!/liquid', '!/liquid/**', '!/liquid/',
|
||||
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
|
||||
'/testnet/api/**', '/signet/api/**'
|
||||
],
|
||||
target: "https://mempool.space",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
},
|
||||
{
|
||||
context: ['/api/v1/ws'],
|
||||
target: "https://mempool.space",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
},
|
||||
{
|
||||
context: ['/api/bisq**', '/bisq/api/**'],
|
||||
target: "https://bisq.markets",
|
||||
pathRewrite: {
|
||||
"^/api/bisq/": "/bisq/api"
|
||||
},
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
},
|
||||
{
|
||||
context: ['/api/liquid**', '/liquid/api/**'],
|
||||
target: "https://liquid.network",
|
||||
pathRewrite: {
|
||||
"^/api/liquid/": "/liquid/api"
|
||||
},
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
},
|
||||
{
|
||||
context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'],
|
||||
target: "https://liquid.network/testnet",
|
||||
pathRewrite: {
|
||||
"^/api/liquidtestnet/": "/liquidtestnet/api"
|
||||
},
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
}
|
||||
];
|
||||
|
||||
if (configContent && configContent.BASE_MODULE == "liquid") {
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'],
|
||||
target: "https://liquid.network",
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
});
|
||||
} else {
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'],
|
||||
target: "https://mempool.space",
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
||||
@@ -1,107 +0,0 @@
|
||||
{
|
||||
"/api/v1": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false
|
||||
},
|
||||
"/api/v1/ws": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"ws": true
|
||||
},
|
||||
"/api/": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/api/": "/api/v1/"
|
||||
}
|
||||
},
|
||||
"/testnet/api/v1": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/testnet/api/v1": "/api/v1"
|
||||
}
|
||||
},
|
||||
"/testnet/api/v1/ws": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/testnet/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/testnet/api/": {
|
||||
"target": "http://localhost:50001/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/testnet/api": ""
|
||||
}
|
||||
},
|
||||
"/signet/api/v1": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/signet/api/v1": "/api/v1"
|
||||
}
|
||||
},
|
||||
"/signet/api/v1/ws": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/signet/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/signet/api/": {
|
||||
"target": "http://localhost:50001/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/signet/api": ""
|
||||
}
|
||||
},
|
||||
"/liquid/api/v1/ws": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/liquid/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/liquid/api/": {
|
||||
"target": "http://localhost:50001/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/liquid/api/": ""
|
||||
}
|
||||
},
|
||||
"/bisq/api/": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/bisq/api/": "/api/v1/bisq/"
|
||||
}
|
||||
},
|
||||
"/bisq/api/v1/ws": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/bisq/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/resources/assets.minimal.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
"/resources/assets.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
"/resources/pools.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
73
frontend/proxy.conf.local.js
Normal file
73
frontend/proxy.conf.local.js
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
let PROXY_CONFIG = require('./proxy.conf.js');
|
||||
const BACKEND_CONFIG_FILE_NAME = '../backend/mempool-config.json';
|
||||
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||
|
||||
let backendConfigContent;
|
||||
let frontendConfigContent;
|
||||
|
||||
// Read frontend config
|
||||
try {
|
||||
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
|
||||
frontendConfigContent = JSON.parse(rawConfig);
|
||||
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.code !== 'ENOENT') {
|
||||
throw new Error(e);
|
||||
} else {
|
||||
console.log(`${FRONTEND_CONFIG_FILE_NAME} file not found, using default config`);
|
||||
}
|
||||
}
|
||||
|
||||
// Read backend config
|
||||
try {
|
||||
const rawConfig = fs.readFileSync(BACKEND_CONFIG_FILE_NAME);
|
||||
backendConfigContent = JSON.parse(rawConfig);
|
||||
console.log(`${BACKEND_CONFIG_FILE_NAME} file found, using provided config`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.code !== 'ENOENT') {
|
||||
throw new Error(e);
|
||||
} else {
|
||||
console.log(`${BACKEND_CONFIG_FILE_NAME} file not found, using default config`);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the "/api/**" entry from the default proxy config
|
||||
let localDevContext = PROXY_CONFIG[0].context
|
||||
|
||||
localDevContext.splice(PROXY_CONFIG[0].context.indexOf('/api/**'), 1);
|
||||
|
||||
PROXY_CONFIG[0].context = localDevContext;
|
||||
|
||||
// Change all targets to localhost
|
||||
PROXY_CONFIG.map(conf => conf.target = "http://localhost:8999");
|
||||
|
||||
// Add rules for local backend
|
||||
if (backendConfigContent) {
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/api/address/**', '/api/tx/**', '/api/block/**', '/api/blocks/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/api/": "/api/v1/"
|
||||
},
|
||||
});
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
console.log(PROXY_CONFIG);
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
||||
@@ -1,99 +0,0 @@
|
||||
{
|
||||
"/api/v1/ws": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"ws": true
|
||||
},
|
||||
"/api": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug",
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/testnet/api/v1/ws": {
|
||||
"target": "https://mempool.space/testnet",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"loglevel": "debug",
|
||||
"pathRewrite": {
|
||||
"^/testnet/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/testnet/api": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": true,
|
||||
"changeOrigin": true,
|
||||
"loglevel": "debug",
|
||||
"pathRewrite": {
|
||||
"/testnet/api": "/testnet/api"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/signet/api/v1/ws": {
|
||||
"target": "https://mempool.space/signet",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"loglevel": "debug",
|
||||
"pathRewrite": {
|
||||
"^/signet/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/signet/api": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": true,
|
||||
"changeOrigin": true,
|
||||
"loglevel": "debug",
|
||||
"pathRewrite": {
|
||||
"/signet/api": "/signet/api"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
|
||||
"/bisq/api/v1/ws": {
|
||||
"target": "https://mempool.space/bisq",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/bisq/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/bisq/api": {
|
||||
"target": "https://mempool.space/bisq",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/bisq/api/": "/api/v1/bisq/"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/liquid/api/v1/ws": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"ws": true
|
||||
},
|
||||
"/liquid/api": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/liquid/api/": "/liquid/api/"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/resources/assets.minimal.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
"/resources/assets.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
"/resources/pools.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
{
|
||||
"/api/v1/ws": {
|
||||
"target": "https://mempool.ninja",
|
||||
"secure": false,
|
||||
"ws": true
|
||||
},
|
||||
"/api/*": {
|
||||
"target": "https://mempool.ninja",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"logLevel": "debug",
|
||||
"pathRewrite": {
|
||||
"^/api": "https://mempool.ninja/api"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/testnet/api/v1/ws": {
|
||||
"target": "https://mempool.ninja/testnet",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/testnet/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/testnet/api/v1/*": {
|
||||
"target": "https://mempool.ninja/testnet",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/testnet/api/v1": "/api/v1"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/signet/api/v1/ws": {
|
||||
"target": "https://mempool.ninja/signet",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/signet/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/signet/api/v1/*": {
|
||||
"target": "https://mempool.ninja/signet",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/signet/api/v1": "/api/v1"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/bisq/api/v1/ws": {
|
||||
"target": "https://mempool.ninja/bisq",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/bisq/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/bisq/api/*": {
|
||||
"target": "https://mempool.ninja/bisq",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/bisq/api/": "/api/v1/bisq/"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/liquid/api/v1/ws": {
|
||||
"target": "https://mempool.ninja/liquid",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"pathRewrite": {
|
||||
"^/liquid/api": "/api/v1/ws"
|
||||
}
|
||||
},
|
||||
"/liquid/api/*": {
|
||||
"target": "https://mempool.ninja/liquid",
|
||||
"secure": false,
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/liquid/api/": "/api/liquid/"
|
||||
},
|
||||
"timeout": 3600000
|
||||
},
|
||||
"/resources/assets.minimal.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
"/resources/assets.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
"/resources/pools.json": {
|
||||
"target": "https://mempool.space",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'zone.js/dist/zone-node';
|
||||
import 'zone.js/node';
|
||||
import './generated-config';
|
||||
|
||||
import * as domino from 'domino';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'zone.js/dist/zone-node';
|
||||
import 'zone.js/node';
|
||||
import './generated-config';
|
||||
|
||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||
|
||||
@@ -14,18 +14,24 @@ import { AssetsComponent } from './assets/assets.component';
|
||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
|
||||
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
|
||||
import { DocsComponent } from './components/docs/docs.component';
|
||||
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
||||
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
|
||||
import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component';
|
||||
import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component';
|
||||
import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
|
||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
|
||||
|
||||
let routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
@@ -60,9 +66,21 @@ let routes: Routes = [
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
component: ApiDocsComponent,
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
@@ -88,12 +106,16 @@ let routes: Routes = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'liquid',
|
||||
path: 'liquidtestnet',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
@@ -136,9 +158,106 @@ let routes: Routes = [
|
||||
path: 'assets',
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
component: ApiDocsComponent,
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'tv',
|
||||
component: TelevisionComponent
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
component: StatusViewComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'liquid',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: DashboardComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
component: BlockComponent
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
component: MempoolBlockComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -163,6 +282,10 @@ let routes: Routes = [
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
@@ -198,9 +321,21 @@ let routes: Routes = [
|
||||
children: [],
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
component: ApiDocsComponent,
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -225,6 +360,10 @@ let routes: Routes = [
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
@@ -260,9 +399,21 @@ let routes: Routes = [
|
||||
children: [],
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
component: ApiDocsComponent,
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -303,7 +454,7 @@ const browserWindow = window || {};
|
||||
// @ts-ignore
|
||||
const browserWindowEnv = browserWindow.__env || {};
|
||||
|
||||
if (browserWindowEnv && browserWindowEnv.OFFICIAL_BISQ_MARKETS) {
|
||||
if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'bisq') {
|
||||
routes = [{
|
||||
path: '',
|
||||
component: BisqMasterPageComponent,
|
||||
@@ -311,6 +462,197 @@ if (browserWindowEnv && browserWindowEnv.OFFICIAL_BISQ_MARKETS) {
|
||||
}];
|
||||
}
|
||||
|
||||
if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
routes = [{
|
||||
path: '',
|
||||
component: LiquidMasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: DashboardComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
component: BlockComponent
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
component: MempoolBlockComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
component: PrivacyPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
component: TrademarkPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'sponsor',
|
||||
component: SponsorComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'testnet',
|
||||
component: LiquidMasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: DashboardComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
component: BlockComponent
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
component: MempoolBlockComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
component: PrivacyPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
component: TrademarkPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'sponsor',
|
||||
component: SponsorComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'tv',
|
||||
component: TelevisionComponent
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
component: StatusViewComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
}];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, {
|
||||
initialNavigation: 'enabled',
|
||||
@@ -320,3 +662,4 @@ if (browserWindowEnv && browserWindowEnv.OFFICIAL_BISQ_MARKETS) {
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
|
||||
|
||||
@@ -31,6 +31,46 @@ export const mempoolFeeColors = [
|
||||
'b9254b',
|
||||
];
|
||||
|
||||
export const chartColors = [
|
||||
"#D81B60",
|
||||
"#8E24AA",
|
||||
"#5E35B1",
|
||||
"#3949AB",
|
||||
"#1E88E5",
|
||||
"#039BE5",
|
||||
"#00ACC1",
|
||||
"#00897B",
|
||||
"#43A047",
|
||||
"#7CB342",
|
||||
"#C0CA33",
|
||||
"#FDD835",
|
||||
"#FFB300",
|
||||
"#FB8C00",
|
||||
"#F4511E",
|
||||
"#6D4C41",
|
||||
"#757575",
|
||||
"#546E7A",
|
||||
"#b71c1c",
|
||||
"#880E4F",
|
||||
"#4A148C",
|
||||
"#311B92",
|
||||
"#1A237E",
|
||||
"#0D47A1",
|
||||
"#01579B",
|
||||
"#006064",
|
||||
"#004D40",
|
||||
"#1B5E20",
|
||||
"#33691E",
|
||||
"#827717",
|
||||
"#F57F17",
|
||||
"#FF6F00",
|
||||
"#E65100",
|
||||
"#BF360C",
|
||||
"#3E2723",
|
||||
"#212121",
|
||||
"#263238",
|
||||
];
|
||||
|
||||
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
||||
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
|
||||
|
||||
@@ -66,7 +106,7 @@ export const languages: Language[] = [
|
||||
// { code: 'lv', name: 'Latviešu' }, // Latvian
|
||||
// { code: 'lt', name: 'Lietuvių' }, // Lithuanian
|
||||
{ code: 'hu', name: 'Magyar' }, // Hungarian
|
||||
// { code: 'mk', name: 'Македонски' }, // Macedonian
|
||||
{ code: 'mk', name: 'Македонски' }, // Macedonian
|
||||
// { code: 'ms', name: 'Bahasa Melayu' }, // Malay
|
||||
{ code: 'nl', name: 'Nederlands' }, // Dutch
|
||||
{ code: 'ja', name: '日本語' }, // Japanese
|
||||
@@ -75,7 +115,7 @@ export const languages: Language[] = [
|
||||
{ code: 'pl', name: 'Polski' }, // Polish
|
||||
{ code: 'pt', name: 'Português' }, // Portuguese
|
||||
// { code: 'pt-BR', name: 'Português (Brazil)' }, // Portuguese (Brazil)
|
||||
// { code: 'ro', name: 'Română' }, // Romanian
|
||||
{ code: 'ro', name: 'Română' }, // Romanian
|
||||
{ code: 'ru', name: 'Русский' }, // Russian
|
||||
// { code: 'sk', name: 'Slovenčina' }, // Slovak
|
||||
{ code: 'sl', name: 'Slovenščina' }, // Slovenian
|
||||
@@ -83,9 +123,20 @@ export const languages: Language[] = [
|
||||
// { code: 'sh', name: 'Srpskohrvatski / српскохрватски' },// Serbo-Croatian
|
||||
{ code: 'fi', name: 'Suomi' }, // Finnish
|
||||
{ code: 'sv', name: 'Svenska' }, // Swedish
|
||||
// { code: 'th', name: 'ไทย' }, // Thai
|
||||
{ code: 'th', name: 'ไทย' }, // Thai
|
||||
{ code: 'tr', name: 'Türkçe' }, // Turkish
|
||||
{ code: 'uk', name: 'Українська' }, // Ukrainian
|
||||
{ code: 'vi', name: 'Tiếng Việt' }, // Vietnamese
|
||||
{ code: 'zh', name: '中文' }, // Chinese
|
||||
];
|
||||
|
||||
export const specialBlocks = {
|
||||
'709632': {
|
||||
labelEvent: 'Taproot 🌱 activation',
|
||||
labelEventCompleted: 'Taproot 🌱 has been activated!',
|
||||
},
|
||||
'840000': {
|
||||
labelEvent: 'Halving 🥳',
|
||||
labelEventCompleted: 'Block Subsidy has halved to 3.125 BTC per block',
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { NgxEchartsModule } from 'ngx-echarts';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './components/app/app.component';
|
||||
@@ -22,19 +23,21 @@ import { AddressLabelsComponent } from './components/address-labels/address-labe
|
||||
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.component';
|
||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||
import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component';
|
||||
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
|
||||
import { AboutComponent } from './components/about/about.component';
|
||||
import { TelevisionComponent } from './components/television/television.component';
|
||||
import { StatisticsComponent } from './components/statistics/statistics.component';
|
||||
import { ChartistComponent } from './components/statistics/chartist.component';
|
||||
import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockchain-blocks.component';
|
||||
import { BlockchainComponent } from './components/blockchain/blockchain.component';
|
||||
import { FooterComponent } from './components/footer/footer.component';
|
||||
import { AudioService } from './services/audio.service';
|
||||
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
|
||||
import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component';
|
||||
import { IncomingTransactionsGraphComponent } from './components/incoming-transactions-graph/incoming-transactions-graph.component';
|
||||
import { TimeSpanComponent } from './components/time-span/time-span.component';
|
||||
import { SeoService } from './services/seo.service';
|
||||
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
|
||||
import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component';
|
||||
import { AssetComponent } from './components/asset/asset.component';
|
||||
import { AssetsComponent } from './assets/assets.component';
|
||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||
@@ -44,16 +47,20 @@ import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { FeesBoxComponent } from './components/fees-box/fees-box.component';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||
import { faAngleDown, faAngleUp, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
|
||||
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
|
||||
import { CodeTemplateComponent } from './components/api-docs/code-template.component';
|
||||
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
|
||||
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
|
||||
import { ApiDocsComponent } from './components/docs/api-docs.component';
|
||||
import { DocsComponent } from './components/docs/docs.component';
|
||||
import { ApiDocsNavComponent } from './components/docs/api-docs-nav.component';
|
||||
import { CodeTemplateComponent } from './components/docs/code-template.component';
|
||||
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
||||
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
|
||||
import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component';
|
||||
import { StorageService } from './services/storage.service';
|
||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||
import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -61,6 +68,7 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
AboutComponent,
|
||||
MasterPageComponent,
|
||||
BisqMasterPageComponent,
|
||||
LiquidMasterPageComponent,
|
||||
TelevisionComponent,
|
||||
BlockchainComponent,
|
||||
StartComponent,
|
||||
@@ -76,11 +84,12 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
TimeSpanComponent,
|
||||
AddressLabelsComponent,
|
||||
MempoolBlocksComponent,
|
||||
ChartistComponent,
|
||||
FooterComponent,
|
||||
MempoolBlockComponent,
|
||||
FeeDistributionGraphComponent,
|
||||
IncomingTransactionsGraphComponent,
|
||||
MempoolGraphComponent,
|
||||
LbtcPegsGraphComponent,
|
||||
AssetComponent,
|
||||
AssetsComponent,
|
||||
MinerComponent,
|
||||
@@ -93,6 +102,9 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
PrivacyPolicyComponent,
|
||||
TrademarkPolicyComponent,
|
||||
SponsorComponent,
|
||||
PushTransactionComponent,
|
||||
DocsComponent,
|
||||
ApiDocsNavComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
@@ -102,8 +114,12 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
BrowserAnimationsModule,
|
||||
InfiniteScrollModule,
|
||||
NgbTypeaheadModule,
|
||||
NgbModule,
|
||||
FontAwesomeModule,
|
||||
SharedModule,
|
||||
NgxEchartsModule.forRoot({
|
||||
echarts: () => import('echarts')
|
||||
})
|
||||
],
|
||||
providers: [
|
||||
ElectrsApiService,
|
||||
@@ -132,6 +148,7 @@ export class AppModule {
|
||||
library.addIcons(faLink);
|
||||
library.addIcons(faBolt);
|
||||
library.addIcons(faTint);
|
||||
library.addIcons(faFilter);
|
||||
library.addIcons(faAngleDown);
|
||||
library.addIcons(faAngleUp);
|
||||
library.addIcons(faExchangeAlt);
|
||||
@@ -145,5 +162,9 @@ export class AppModule {
|
||||
library.addIcons(faSortUp);
|
||||
library.addIcons(faCaretUp);
|
||||
library.addIcons(faCaretDown);
|
||||
library.addIcons(faAngleRight);
|
||||
library.addIcons(faAngleLeft);
|
||||
library.addIcons(faBook);
|
||||
library.addIcons(faListUl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="container-xl">
|
||||
<h1 style="float: left;" i18n="Registered assets page header">Registered assets</h1>
|
||||
<br>
|
||||
|
||||
<div class="title-asset">
|
||||
<h1 i18n="Registered assets page header">Registered assets</h1>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<form [formGroup]="searchForm" class="form-inline">
|
||||
<div class="input-group m-2">
|
||||
<div class="input-group mb-2">
|
||||
<input style="width: 250px;" formControlName="searchText" type="text" class="form-control" i18n-placeholder="Search Assets Placeholder Text" placeholder="Search asset">
|
||||
<div class="input-group-append">
|
||||
<button [disabled]="!searchForm.get('searchText')?.value.length" class="btn btn-secondary" type="button" (click)="searchForm.get('searchText')?.setValue('');" autocomplete="off" i18n="Search Clear Button">Clear</button>
|
||||
@@ -20,16 +20,14 @@
|
||||
<th i18n="Asset ticker header">Ticker</th>
|
||||
<th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
|
||||
<th i18n="Asset ID header">Asset ID</th>
|
||||
<th class="d-none d-lg-block" i18n="Asset issuance transaction header">Issuance TX</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let asset of filteredAssets; trackBy: trackByAsset">
|
||||
<td class="td-name">{{ asset.name }}</td>
|
||||
<td class="td-name"><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a></td>
|
||||
<td>{{ asset.ticker }}</td>
|
||||
<td class="d-none d-md-block"><a *ngIf="asset.entity" target="_blank" href="{{ 'http://' + asset.entity.domain }}">{{ asset.entity.domain }}</a></td>
|
||||
<td class="d-none d-md-block">{{ asset.entity && asset.entity.domain }}</td>
|
||||
<td><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
|
||||
<td class="d-none d-lg-block"><ng-template [ngIf]="asset.issuance_txin"><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></ng-template></td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -47,16 +45,14 @@
|
||||
<th i18n="Asset ticker header">Ticker</th>
|
||||
<th i18n="Asset Issuer Domain header">Issuer domain</th>
|
||||
<th i18n="Asset ID header">Asset ID</th>
|
||||
<th i18n="Asset issuance transaction header">Issuance TX</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let dummy of [0,0,0]">
|
||||
<tr *ngFor="let dummy of [0,0,0,0,0,0,0,0,0,0]">
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
<td class="d-none d-md-block"><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
<td class="d-none d-lg-block"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -72,4 +68,4 @@
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
@@ -3,4 +3,11 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.title-asset {
|
||||
h1 {
|
||||
line-height: 1;
|
||||
margin: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { merge, combineLatest, Observable } from 'rxjs';
|
||||
import { AssetExtended } from '../interfaces/electrs.interface';
|
||||
import { SeoService } from '../services/seo.service';
|
||||
import { StateService } from '../services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-assets',
|
||||
@@ -15,7 +16,8 @@ import { SeoService } from '../services/seo.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AssetsComponent implements OnInit {
|
||||
nativeAssetId = environment.nativeAssetId;
|
||||
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
|
||||
|
||||
assets: AssetExtended[];
|
||||
assetsCache: AssetExtended[];
|
||||
searchForm: FormGroup;
|
||||
@@ -34,6 +36,7 @@ export class AssetsComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private seoService: SeoService,
|
||||
private stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
@@ -52,12 +55,22 @@ export class AssetsComponent implements OnInit {
|
||||
take(1),
|
||||
mergeMap(([assets, qp]) => {
|
||||
this.assets = Object.values(assets);
|
||||
// @ts-ignore
|
||||
this.assets.push({
|
||||
name: 'Liquid Bitcoin',
|
||||
ticker: 'L-BTC',
|
||||
asset_id: this.nativeAssetId,
|
||||
});
|
||||
if (this.stateService.network === 'liquid') {
|
||||
// @ts-ignore
|
||||
this.assets.push({
|
||||
name: 'Liquid Bitcoin',
|
||||
ticker: 'L-BTC',
|
||||
asset_id: this.nativeAssetId,
|
||||
});
|
||||
} else if (this.stateService.network === 'liquidtestnet') {
|
||||
// @ts-ignore
|
||||
this.assets.push({
|
||||
name: 'Test Liquid Bitcoin',
|
||||
ticker: 'tL-BTC',
|
||||
asset_id: this.nativeAssetId,
|
||||
});
|
||||
}
|
||||
|
||||
this.assets = this.assets.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
this.assetsCache = this.assets;
|
||||
this.searchForm.get('searchText').enable();
|
||||
@@ -107,7 +120,7 @@ export class AssetsComponent implements OnInit {
|
||||
const start = (this.page - 1) * this.itemsPerPage;
|
||||
if (searchText.length ) {
|
||||
const filteredAssets = this.assetsCache.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
|
||||
|| asset.ticker.toLowerCase().indexOf(searchText.toLowerCase()) > -1);
|
||||
|| (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1);
|
||||
this.assets = filteredAssets;
|
||||
return filteredAssets.slice(start, this.itemsPerPage + start);
|
||||
} else {
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<span style="float: left;" class="d-none d-md-block">{{ tx.id }}</span>
|
||||
</a>
|
||||
<div class="float-right">
|
||||
{{ tx.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
‎{{ tx.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="title-block">
|
||||
<h1><ng-template [ngIf]="blockHeight" i18n="block.block">Block <a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
|
||||
<h1><ng-template [ngIf]="blockHeight" i18n="shared.block-title">Block <ng-container *ngTemplateOutlet="blockTemplateContent"></ng-container></ng-template></h1>
|
||||
</div>
|
||||
|
||||
<ng-template #blockTemplateContent><a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<ng-template [ngIf]="!isLoading && !error">
|
||||
@@ -20,7 +22,7 @@
|
||||
<tr>
|
||||
<td i18n="transaction.timestamp|Transaction Timestamp">Timestamp</td>
|
||||
<td>
|
||||
{{ block.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
‎{{ block.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time-since [time]="block.time / 1000" [fastRender]="true"></app-time-since>)</i>
|
||||
</div>
|
||||
@@ -58,7 +60,7 @@
|
||||
<span style="float: left;" class="d-none d-md-block">{{ tx.id }}</span>
|
||||
</a>
|
||||
<div class="float-right">
|
||||
{{ tx.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
‎{{ tx.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<div class="container-info">
|
||||
<h1>
|
||||
<ng-template [ngIf]="stateService.env.OFFICIAL_BISQ_MARKETS" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
|
||||
<ng-template [ngIf]="stateService.env.BASE_MODULE === 'bisq'" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
|
||||
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
|
||||
</h1>
|
||||
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
|
||||
|
||||
@@ -17,19 +17,6 @@
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow: scroll;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
font-size: 13px;
|
||||
@media(min-width: 576px){
|
||||
font-size: 16px;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.container-info{
|
||||
overflow-x: scroll;
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export class BisqDashboardComponent implements OnInit {
|
||||
const newTickers = [];
|
||||
for (const t in tickers) {
|
||||
|
||||
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||
if (this.stateService.env.BASE_MODULE !== 'bisq') {
|
||||
const pair = t.split('_');
|
||||
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
|
||||
continue;
|
||||
@@ -106,7 +106,7 @@ export class BisqDashboardComponent implements OnInit {
|
||||
])
|
||||
.pipe(
|
||||
map(([trades, markets]) => {
|
||||
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||
if (this.stateService.env.BASE_MODULE !== 'bisq') {
|
||||
trades = trades.filter((trade) => {
|
||||
const pair = trade.market.split('_');
|
||||
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center">
|
||||
<ng-template [ngIf]="stateService.env.OFFICIAL_BISQ_MARKETS" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
|
||||
<ng-template [ngIf]="stateService.env.BASE_MODULE === 'bisq'" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
|
||||
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
|
||||
</h5>
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<app-language-selector *ngIf="!stateService.env.OFFICIAL_BISQ_MARKETS"></app-language-selector>
|
||||
<app-language-selector *ngIf="stateService.env.BASE_MODULE !== 'bisq'"></app-language-selector>
|
||||
|
||||
<div class="text-small text-center mt-3">
|
||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||
|
||||
@@ -18,15 +18,44 @@
|
||||
}
|
||||
|
||||
.table-container {
|
||||
font-size: 13px;
|
||||
@media(min-width: 576px){
|
||||
font-size: 16px;
|
||||
overflow: scroll;
|
||||
scrollbar-width: none;
|
||||
font-size: 13px;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@media(min-width: 576px){
|
||||
font-size: 16px;
|
||||
}
|
||||
thead th{
|
||||
text-align: right;
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
&:nth-child(3) {
|
||||
display: none;
|
||||
@media(min-width: 1100px){
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
tr {
|
||||
td {
|
||||
text-align: right;
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
display: none;
|
||||
@media(min-width: 1100px){
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
}
|
||||
@@ -41,44 +70,43 @@
|
||||
background-color: #1d1f31;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.card-title {
|
||||
color: #4a68b9;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.info-block {
|
||||
float: left;
|
||||
width: 350px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
|
||||
.progress {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
background-color: #2d3348;
|
||||
height: 1.1rem;
|
||||
}
|
||||
|
||||
|
||||
.bg-warning {
|
||||
background-color: #b58800 !important;
|
||||
}
|
||||
|
||||
|
||||
.skeleton-loader {
|
||||
max-width: 100%;
|
||||
&.shorter {
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.more-padding {
|
||||
padding: 1.25rem 2rem 1.25rem 2rem;
|
||||
}
|
||||
|
||||
|
||||
.graph-card {
|
||||
height: 100%;
|
||||
@media (min-width: 992px) {
|
||||
height: 385px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export class BisqMainDashboardComponent implements OnInit {
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle(`Markets`);
|
||||
this.seoService.resetTitle();
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.usdPrice$ = this.stateService.conversions$.asObservable().pipe(
|
||||
@@ -77,7 +77,7 @@ export class BisqMainDashboardComponent implements OnInit {
|
||||
const newTickers = [];
|
||||
for (const t in tickers) {
|
||||
|
||||
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||
if (this.stateService.env.BASE_MODULE !== 'bisq') {
|
||||
const pair = t.split('_');
|
||||
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
|
||||
continue;
|
||||
@@ -114,7 +114,7 @@ export class BisqMainDashboardComponent implements OnInit {
|
||||
])
|
||||
.pipe(
|
||||
map(([trades, markets]) => {
|
||||
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||
if (this.stateService.env.BASE_MODULE !== 'bisq') {
|
||||
trades = trades.filter((trade) => {
|
||||
const pair = trade.market.split('_');
|
||||
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<tbody *ngIf="(trades$ | async) as trades; else loadingTmpl">
|
||||
<tr *ngFor="let trade of trades;">
|
||||
<td>
|
||||
{{ trade.trade_date | date:'yyyy-MM-dd HH:mm' }}
|
||||
‎{{ trade.trade_date | date:'yyyy-MM-dd HH:mm' }}
|
||||
</td>
|
||||
<td *ngIf="view === 'all'">
|
||||
<ng-container *ngIf="(trade._market || market).rtype === 'fiat'; else priceCrypto"><span class="green-color">{{ trade.price | currency: (trade._market || market).rsymbol }}</span></ng-container>
|
||||
|
||||
@@ -1,12 +1,38 @@
|
||||
|
||||
.table-container {
|
||||
overflow: scroll;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
font-size: 13px;
|
||||
@media(min-width: 576px){
|
||||
font-size: 16px;
|
||||
overflow: scroll;
|
||||
scrollbar-width: none;
|
||||
font-size: 13px;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@media(min-width: 576px){
|
||||
font-size: 16px;
|
||||
}
|
||||
thead th{
|
||||
text-align: right;
|
||||
&:first-child{
|
||||
text-align: left;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
&:nth-child(2) {
|
||||
display: none;
|
||||
@media(min-width: 1100px){
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
tr {
|
||||
td {
|
||||
text-align: right;
|
||||
&:first-child{
|
||||
text-align: left;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
display: none;
|
||||
@media(min-width: 1100px){
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="title-block">
|
||||
<ng-template [ngIf]="!isLoading && !error">
|
||||
|
||||
<div>
|
||||
<div class="title-block">
|
||||
<div class="title">
|
||||
<h1 i18n="shared.transaction">Transaction</h1>
|
||||
</div>
|
||||
|
||||
<div class="tx-link">
|
||||
<span class="tx-link float-left">
|
||||
<a [routerLink]="['/bisq-tx' | relativeUrl, bisqTx.id]">
|
||||
<span class="d-inline d-lg-none">{{ bisqTx.id | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ bisqTx.id }}</span>
|
||||
</a>
|
||||
<app-clipboard [text]="bisqTx.id"></app-clipboard>
|
||||
</div>
|
||||
</span>
|
||||
<span class="grow"></span>
|
||||
<div class="container-buttons">
|
||||
<button *ngIf="(latestBlock$ | async) as latestBlock" type="button" class="btn btn-sm btn-success float-right">
|
||||
<ng-container *ngTemplateOutlet="latestBlock.height - bisqTx.blockHeight + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - bisqTx.blockHeight + 1}"></ng-container>
|
||||
@@ -22,6 +21,8 @@
|
||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="box transaction-container">
|
||||
@@ -32,7 +33,7 @@
|
||||
<tr>
|
||||
<td i18n="transaction.timestamp|Transaction Timestamp">Timestamp</td>
|
||||
<td>
|
||||
{{ bisqTx.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
‎{{ bisqTx.time | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time-since [time]="bisqTx.time / 1000" [fastRender]="true"></app-time-since>)</i>
|
||||
</div>
|
||||
@@ -67,7 +68,7 @@
|
||||
<tr>
|
||||
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
|
||||
<td *ngIf="!isLoadingTx; else loadingTxFee">
|
||||
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span class="symbol">sat/vB</span>
|
||||
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol">sat/vB</span>
|
||||
|
||||
<app-tx-fee-rating [tx]="tx"></app-tx-fee-rating>
|
||||
</td>
|
||||
@@ -84,25 +85,33 @@
|
||||
|
||||
<br>
|
||||
|
||||
<h2 i18n="transaction.details">Details</h2>
|
||||
|
||||
<div class="title">
|
||||
<h2 i18n="transaction.details">Details</h2>
|
||||
</div>
|
||||
|
||||
<app-bisq-transaction-details [tx]="bisqTx"></app-bisq-transaction-details>
|
||||
|
||||
<br>
|
||||
|
||||
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
|
||||
<div class="title">
|
||||
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
|
||||
</div>
|
||||
|
||||
<app-bisq-transfers [tx]="bisqTx"></app-bisq-transfers>
|
||||
|
||||
<br>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template [ngIf="isLoading && !error">
|
||||
<ng-template [ngIf]="isLoading && !error">
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="title-block">
|
||||
<div class="title">
|
||||
<h1 i18n="shared.transaction">Transaction</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
@@ -112,6 +121,14 @@
|
||||
<td class="td-width"><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-width"><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-width"><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -122,6 +139,10 @@
|
||||
<td class="td-width"><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-width"><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -130,7 +151,10 @@
|
||||
|
||||
<br>
|
||||
|
||||
<h2 i18n="transaction.details">Details</h2>
|
||||
<div class="title">
|
||||
<h2 i18n="transaction.details">Details</h2>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
@@ -151,18 +175,30 @@
|
||||
|
||||
<br>
|
||||
|
||||
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
|
||||
|
||||
<div class="title">
|
||||
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -178,4 +214,4 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,11 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (tx.version) {
|
||||
this.router.navigate(['/tx/', this.txId], { state: { data: tx, bsqTx: true }});
|
||||
if (this.stateService.env.BASE_MODULE === 'bisq') {
|
||||
window.location.replace('https://mempool.space/tx/' + this.txId);
|
||||
} else {
|
||||
this.router.navigate(['/tx/', this.txId], { state: { data: tx, bsqTx: true }});
|
||||
}
|
||||
return of(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||
import { BisqTransaction, BisqOutput } from '../bisq.interfaces';
|
||||
|
||||
import { merge, Observable } from 'rxjs';
|
||||
import { switchMap, map, tap, filter } from 'rxjs/operators';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { switchMap, map, tap } from 'rxjs/operators';
|
||||
import { BisqApiService } from '../bisq-api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
@@ -16,7 +16,7 @@ import { WebsocketService } from 'src/app/services/websocket.service';
|
||||
styleUrls: ['./bisq-transactions.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class BisqTransactionsComponent implements OnInit {
|
||||
export class BisqTransactionsComponent implements OnInit, OnDestroy {
|
||||
transactions$: Observable<[BisqTransaction[], number]>;
|
||||
page = 1;
|
||||
itemsPerPage = 50;
|
||||
@@ -25,6 +25,7 @@ export class BisqTransactionsComponent implements OnInit {
|
||||
loadingItems: number[];
|
||||
radioGroupForm: FormGroup;
|
||||
types: string[] = [];
|
||||
radioGroupSubscription: Subscription;
|
||||
|
||||
txTypeOptions: IMultiSelectOption[] = [
|
||||
{ id: 1, name: $localize`Asset listing fee` },
|
||||
@@ -90,52 +91,39 @@ export class BisqTransactionsComponent implements OnInit {
|
||||
this.paginationMaxSize = 3;
|
||||
}
|
||||
|
||||
this.transactions$ = merge(
|
||||
this.route.queryParams
|
||||
.pipe(
|
||||
filter((queryParams) => {
|
||||
const newPage = parseInt(queryParams.page, 10);
|
||||
const types = queryParams.types;
|
||||
if (newPage !== this.page || types !== this.types.map((type) => this.txTypes.indexOf(type) + 1).join(',')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
tap((queryParams) => {
|
||||
if (queryParams.page) {
|
||||
const newPage = parseInt(queryParams.page, 10);
|
||||
this.page = newPage;
|
||||
} else {
|
||||
this.page = 1;
|
||||
}
|
||||
if (queryParams.types) {
|
||||
const types = queryParams.types.split(',').map((str: string) => parseInt(str, 10));
|
||||
this.types = types.map((id: number) => this.txTypes[id - 1]);
|
||||
this.radioGroupForm.get('txTypes').setValue(types, { emitEvent: false });
|
||||
} else {
|
||||
this.types = [];
|
||||
this.radioGroupForm.get('txTypes').setValue(this.txTypesDefaultChecked, { emitEvent: false });
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
})
|
||||
),
|
||||
this.radioGroupForm.valueChanges
|
||||
.pipe(
|
||||
tap((data) => {
|
||||
this.types = data.txTypes.map((id: number) => this.txTypes[id - 1]);
|
||||
if (this.types.length === this.txTypes.length) {
|
||||
this.types = [];
|
||||
}
|
||||
this.page = 1;
|
||||
this.typesChanged(data.txTypes);
|
||||
this.cd.markForCheck();
|
||||
})
|
||||
),
|
||||
)
|
||||
this.transactions$ = this.route.queryParams
|
||||
.pipe(
|
||||
tap((queryParams) => {
|
||||
if (queryParams.page) {
|
||||
const newPage = parseInt(queryParams.page, 10);
|
||||
this.page = newPage;
|
||||
} else {
|
||||
this.page = 1;
|
||||
}
|
||||
if (queryParams.types) {
|
||||
const types = queryParams.types.split(',').map((str: string) => parseInt(str, 10));
|
||||
this.types = types.map((id: number) => this.txTypes[id - 1]);
|
||||
this.radioGroupForm.get('txTypes').setValue(types, { emitEvent: false });
|
||||
} else {
|
||||
this.types = [];
|
||||
this.radioGroupForm.get('txTypes').setValue([], { emitEvent: false });
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
}),
|
||||
switchMap(() => this.bisqApiService.listTransactions$((this.page - 1) * this.itemsPerPage, this.itemsPerPage, this.types)),
|
||||
map((response) => [response.body, parseInt(response.headers.get('x-total-count'), 10)])
|
||||
);
|
||||
|
||||
this.radioGroupSubscription = this.radioGroupForm.valueChanges
|
||||
.subscribe((data) => {
|
||||
this.types = data.txTypes.map((id: number) => this.txTypes[id - 1]);
|
||||
if (this.types.length === this.txTypes.length) {
|
||||
this.types = [];
|
||||
}
|
||||
this.page = 1;
|
||||
this.typesChanged(data.txTypes);
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
pageChange(page: number) {
|
||||
@@ -144,8 +132,6 @@ export class BisqTransactionsComponent implements OnInit {
|
||||
queryParams: { page: page },
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
// trigger queryParams change
|
||||
this.page = -1;
|
||||
}
|
||||
|
||||
typesChanged(types: number[]) {
|
||||
@@ -172,4 +158,8 @@ export class BisqTransactionsComponent implements OnInit {
|
||||
onResize(event: any) {
|
||||
this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.radioGroupSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ interface BisqScriptPubKey {
|
||||
addresses: string[];
|
||||
asm: string;
|
||||
hex: string;
|
||||
reqSigs: number;
|
||||
reqSigs?: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ import { BisqBlockComponent } from './bisq-block/bisq-block.component';
|
||||
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
||||
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
||||
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
||||
import { ApiDocsComponent } from '../components/api-docs/api-docs.component';
|
||||
import { DocsComponent } from '../components/docs/docs.component';
|
||||
import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.component';
|
||||
import { BisqMarketComponent } from './bisq-market/bisq-market.component';
|
||||
import { BisqMainDashboardComponent } from './bisq-main-dashboard/bisq-main-dashboard.component';
|
||||
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
|
||||
import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -30,6 +31,10 @@ const routes: Routes = [
|
||||
path: 'market/:pair',
|
||||
component: BisqMarketComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: BisqTransactionComponent
|
||||
@@ -55,9 +60,21 @@ const routes: Routes = [
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'docs/api/:type',
|
||||
component: DocsComponent
|
||||
},
|
||||
{
|
||||
path: 'docs/api',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
component: ApiDocsComponent,
|
||||
redirectTo: 'docs/api/rest'
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
|
||||
@@ -16,6 +16,7 @@ export function calcSegwitFeeGains(tx: Transaction) {
|
||||
const isP2sh = vin.prevout.scriptpubkey_type === 'p2sh';
|
||||
const isP2wsh = vin.prevout.scriptpubkey_type === 'v0_p2wsh';
|
||||
const isP2wpkh = vin.prevout.scriptpubkey_type === 'v0_p2wpkh';
|
||||
const isP2tr = vin.prevout.scriptpubkey_type === 'v1_p2tr';
|
||||
|
||||
const op = vin.scriptsig ? vin.scriptsig_asm.split(' ')[0] : null;
|
||||
const isP2sh2Wpkh = isP2sh && !!vin.witness && op === 'OP_PUSHBYTES_22';
|
||||
@@ -25,6 +26,7 @@ export function calcSegwitFeeGains(tx: Transaction) {
|
||||
// Native Segwit - P2WPKH/P2WSH (Bech32)
|
||||
case isP2wpkh:
|
||||
case isP2wsh:
|
||||
case isP2tr:
|
||||
// maximal gains: the scriptSig is moved entirely to the witness part
|
||||
realizedGains += witnessSize(vin) * 3;
|
||||
// XXX P2WSH output creation is more expensive, should we take this into consideration?
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="about-text">
|
||||
<div class="about-text" *ngIf="stateService.env.BASE_MODULE === 'mempool'; else marginBox">
|
||||
<h5><ng-container i18n="about.about-the-project">The Mempool Open Source Project</ng-container><ng-template [ngIf]="locale.substr(0, 2) === 'en'"> ™</ng-template></h5>
|
||||
<p i18n>Building a mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, without any advertising, altcoins, or third-party trackers.</p>
|
||||
</div>
|
||||
<ng-template #marginBox>
|
||||
<div class="no-about-margin"></div>
|
||||
</ng-template>
|
||||
|
||||
<div class="social-icons">
|
||||
<a target="_blank" href="https://github.com/mempool/mempool">
|
||||
@@ -20,20 +23,20 @@
|
||||
<a target="_blank" href="https://twitter.com/mempool">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://t.me/mempoolspace">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M446.7 98.6l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1L117.8 284 16.2 252.2c-22.1-6.9-22.5-22.1 4.6-32.7L418.2 66.4c18.4-6.9 34.5 4.1 28.5 32.2z"></path></svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://keybase.io/team/mempool">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M286.17 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18zm111.92-147.6c-9.5-14.62-39.37-52.45-87.26-73.71q-9.1-4.06-18.38-7.27a78.43 78.43 0 0 0-47.88-104.13c-12.41-4.1-23.33-6-32.41-5.77-.6-2-1.89-11 9.4-35L198.66 32l-5.48 7.56c-8.69 12.06-16.92 23.55-24.34 34.89a51 51 0 0 0-8.29-1.25c-41.53-2.45-39-2.33-41.06-2.33-50.61 0-50.75 52.12-50.75 45.88l-2.36 36.68c-1.61 27 19.75 50.21 47.63 51.85l8.93.54a214 214 0 0 0-46.29 35.54C14 304.66 14 374 14 429.77v33.64l23.32-29.8a148.6 148.6 0 0 0 14.56 37.56c5.78 10.13 14.87 9.45 19.64 7.33 4.21-1.87 10-6.92 3.75-20.11a178.29 178.29 0 0 1-15.76-53.13l46.82-59.83-24.66 74.11c58.23-42.4 157.38-61.76 236.25-38.59 34.2 10.05 67.45.69 84.74-23.84.72-1 1.2-2.16 1.85-3.22a156.09 156.09 0 0 1 2.8 28.43c0 23.3-3.69 52.93-14.88 81.64-2.52 6.46 1.76 14.5 8.6 15.74 7.42 1.57 15.33-3.1 18.37-11.15C429 443 434 414 434 382.32c0-38.58-13-77.46-35.91-110.92zM142.37 128.58l-15.7-.93-1.39 21.79 13.13.78a93 93 0 0 0 .32 19.57l-22.38-1.34a12.28 12.28 0 0 1-11.76-12.79L107 119c1-12.17 13.87-11.27 13.26-11.32l29.11 1.73a144.35 144.35 0 0 0-7 19.17zm148.42 172.18a10.51 10.51 0 0 1-14.35-1.39l-9.68-11.49-34.42 27a8.09 8.09 0 0 1-11.13-1.08l-15.78-18.64a7.38 7.38 0 0 1 1.34-10.34l34.57-27.18-14.14-16.74-17.09 13.45a7.75 7.75 0 0 1-10.59-1s-3.72-4.42-3.8-4.53a7.38 7.38 0 0 1 1.37-10.34L214 225.19s-18.51-22-18.6-22.14a9.56 9.56 0 0 1 1.74-13.42 10.38 10.38 0 0 1 14.3 1.37l81.09 96.32a9.58 9.58 0 0 1-1.74 13.44zM187.44 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18z"></path></svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://matrix.to/#/#mempool:bitcoin.kyoto">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="matrix" class="svg-inline--fa fa-matrix fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 1792"><path fill="currentColor" d="M40.467 163.152v1465.696H145.92V1664H0V128h145.92v35.152zm450.757 464.64v74.14h2.069c19.79-28.356 43.717-50.215 71.483-65.575 27.765-15.656 59.963-23.336 96-23.336 34.56 0 66.165 6.795 94.818 20.086 28.652 13.293 50.216 37.22 65.28 70.893 16.246-23.926 38.4-45.194 66.166-63.507 27.766-18.314 60.848-27.472 98.954-27.472 28.948 0 55.828 3.545 80.64 10.635 24.812 7.088 45.785 18.314 63.508 33.968 17.722 15.656 31.31 35.742 41.354 60.85 9.747 25.107 14.768 55.236 14.768 90.683v366.573h-150.35V865.28c0-18.314-.59-35.741-2.068-51.987-1.476-16.247-5.316-30.426-11.52-42.24-6.499-12.112-15.656-21.563-28.062-28.653-12.405-7.088-29.242-10.634-50.214-10.634-21.268 0-38.4 4.135-51.397 12.112-12.997 8.27-23.336 18.608-30.72 31.901-7.386 12.997-12.407 27.765-14.77 44.602-2.363 16.542-3.84 33.379-3.84 50.216v305.133H692.971v-307.2c0-16.247-.294-32.197-1.18-48.149-.591-15.95-3.84-30.424-9.157-44.011-5.317-13.293-14.178-24.223-26.585-32.197-12.406-7.976-30.425-12.112-54.646-12.112-7.088 0-16.542 1.478-28.062 4.726-11.52 3.25-23.04 9.157-33.968 18.02-10.93 8.86-20.383 21.563-28.063 38.103-7.68 16.543-11.52 38.4-11.52 65.28v317.834H349.44V627.792zm1004.309 1001.056V163.152H1390.08V128H1536v1536h-145.92v-35.152z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="enterprise-sponsor">
|
||||
<h3 i18n="about.sponsors.enterprise.withRocket">Enterprise Sponsors 🚀</h3>
|
||||
<div class="wrapper">
|
||||
<a href="https://squarecrypto.org/" target="_blank" title="Square Crypto">
|
||||
<img class="image" src="/resources/profile/sqcrypto.svg" />
|
||||
<span>Square</span>
|
||||
<a href="https://spiral.xyz/" target="_blank" title="Spiral">
|
||||
<img class="image" src="/resources/profile/spiral.svg" />
|
||||
<span>Spiral</span>
|
||||
</a>
|
||||
<a href="https://gemini.com/" target="_blank" title="Gemini">
|
||||
<img class="image" src="/resources/profile/gemini.svg" />
|
||||
@@ -47,6 +50,14 @@
|
||||
<img class="image" src="/resources/profile/foundry.svg" />
|
||||
<span>Foundry</span>
|
||||
</a>
|
||||
<a href="https://unchained.com/" target="_blank" title="Unchained">
|
||||
<img class="image" src="/resources/profile/unchained.svg" />
|
||||
<span>Unchained</span>
|
||||
</a>
|
||||
<a href="https://blockstream.com/" target="_blank" title="Blockstream">
|
||||
<img class="image" src="/resources/profile/blockstream.svg" />
|
||||
<span>Blockstream</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,6 +102,10 @@
|
||||
<img class="image" src="/resources/profile/ronindojo.png" />
|
||||
<span>RoninDojo</span>
|
||||
</a>
|
||||
<a href="https://github.com/runcitadel/dashboard" target="_blank" title="Citadel">
|
||||
<img class="image" src="/resources/profile/runcitadel.svg" />
|
||||
<span>Citadel</span>
|
||||
</a>
|
||||
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
||||
<img class="image" src="/resources/profile/electrum.jpg" />
|
||||
<span>Electrum</span>
|
||||
@@ -119,6 +134,10 @@
|
||||
<img class="image" src="/resources/profile/blixt.png" />
|
||||
<span>Blixt</span>
|
||||
</a>
|
||||
<a href="https://github.com/ZeusLN/zeus" target="_blank" title="Zeus">
|
||||
<img class="image" src="/resources/profile/zeus.png" />
|
||||
<span>Zeus</span>
|
||||
</a>
|
||||
<a href="https://github.com/vulpemventures/marina" target="_blank" title="Marina Wallet">
|
||||
<img class="image" src="/resources/profile/marina.svg" />
|
||||
<span>Marina</span>
|
||||
@@ -127,14 +146,6 @@
|
||||
<img class="image" src="/resources/profile/satpile.jpg" />
|
||||
<span>Satpile</span>
|
||||
</a>
|
||||
<a href="https://github.com/btcontract/lnwallet" target="_blank" title="Bitcoin Lightning Wallet">
|
||||
<img class="image" src="/resources/profile/blw.png" />
|
||||
<span>BLW</span>
|
||||
</a>
|
||||
<a href="https://github.com/pxsocs/warden" target="_blank" title="WARden Portfolio">
|
||||
<img class="image" src="/resources/profile/warden.jpg" />
|
||||
<span>WARden</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -152,20 +163,32 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contributors">
|
||||
<h3 i18n="about.contributors">Project Contributors</h3>
|
||||
<div class="wrapper">
|
||||
<ng-container *ngIf="contributors$ | async as contributors; else loadingSponsors">
|
||||
<ng-template ngFor let-contributor [ngForOf]="contributors">
|
||||
|
||||
<ng-container *ngIf="allContributors$ | async as contributors else loadingSponsors">
|
||||
<div class="contributors">
|
||||
<h3 i18n="about.contributors">Project Contributors</h3>
|
||||
<div class="wrapper">
|
||||
<ng-template ngFor let-contributor [ngForOf]="contributors.regular">
|
||||
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
|
||||
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" />
|
||||
<span>{{ contributor.name }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="maintainers" *ngIf="contributors.core.length">
|
||||
<h3 i18n="about.project_members">Project Members</h3>
|
||||
<div class="wrapper">
|
||||
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
|
||||
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
|
||||
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" />
|
||||
<span>{{ contributor.name }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="maintainers">
|
||||
<h3 i18n="about.maintainers">Project Maintainers</h3>
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
img {
|
||||
box-shadow: 0px 0px 20px #1bd8f4;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
img, span{
|
||||
@@ -176,4 +176,8 @@
|
||||
.footer-version {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-about-margin {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Observable } from 'rxjs';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { IBackendInfo } from 'src/app/interfaces/websocket.interface';
|
||||
import { Router } from '@angular/router';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about',
|
||||
@@ -16,7 +17,7 @@ import { Router } from '@angular/router';
|
||||
export class AboutComponent implements OnInit {
|
||||
backendInfo$: Observable<IBackendInfo>;
|
||||
sponsors$: Observable<any>;
|
||||
contributors$: Observable<any>;
|
||||
allContributors$: Observable<any>;
|
||||
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH;
|
||||
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
|
||||
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
||||
@@ -25,7 +26,7 @@ export class AboutComponent implements OnInit {
|
||||
constructor(
|
||||
private websocketService: WebsocketService,
|
||||
private seoService: SeoService,
|
||||
private stateService: StateService,
|
||||
public stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
private router: Router,
|
||||
@Inject(LOCALE_ID) public locale: string,
|
||||
@@ -37,11 +38,18 @@ export class AboutComponent implements OnInit {
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.sponsors$ = this.apiService.getDonation$();
|
||||
this.contributors$ = this.apiService.getContributor$();
|
||||
this.allContributors$ = this.apiService.getContributor$().pipe(
|
||||
map((contributors) => {
|
||||
return {
|
||||
regular: contributors.filter((user) => !user.core_constributor),
|
||||
core: contributors.filter((user) => user.core_constributor),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sponsor() {
|
||||
if (this.officialMempoolSpace) {
|
||||
if (this.officialMempoolSpace && this.stateService.env.BASE_MODULE === 'mempool') {
|
||||
this.router.navigateByUrl('/sponsor');
|
||||
} else {
|
||||
this.showNavigateToSponsor = true;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<div class="container-xl">
|
||||
<h1 i18n="shared.address">Address</h1>
|
||||
<div class="tx-link">
|
||||
<a [routerLink]="['/address/' | relativeUrl, addressString]" >
|
||||
<span class="d-inline d-lg-none">{{ addressString | shortenString : 18 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
||||
</a>
|
||||
<app-clipboard [text]="addressString"></app-clipboard>
|
||||
<div class="title-address">
|
||||
<h1 i18n="shared.address">Address</h1>
|
||||
<div class="tx-link">
|
||||
<a [routerLink]="['/address/' | relativeUrl, addressString]" >
|
||||
<span class="d-inline d-lg-none">{{ addressString | shortenString : 18 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
||||
</a>
|
||||
<app-clipboard [text]="addressString"></app-clipboard>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@@ -18,10 +19,17 @@
|
||||
<div class="col-md">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr *ngIf="addressInfo && addressInfo.unconfidential">
|
||||
<td i18n="address.unconfidential">Unconfidential</td>
|
||||
<td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">
|
||||
<span class="d-inline d-lg-none">{{ addressInfo.unconfidential | shortenString : 14 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ addressInfo.unconfidential }}</span>
|
||||
</a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="!address.electrum">
|
||||
<tr>
|
||||
<td i18n="address.total-received">Total received</td>
|
||||
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="receieved" [noFiat]="true"></app-amount></td>
|
||||
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="received" [noFiat]="true"></app-amount></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Total sent</td>
|
||||
@@ -30,7 +38,7 @@
|
||||
</ng-template>
|
||||
<tr>
|
||||
<td i18n="address.balance">Balance</td>
|
||||
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="receieved - sent" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="receieved - sent"></app-fiat></span></td>
|
||||
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="received - sent" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="received - sent"></app-fiat></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -46,12 +54,13 @@
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<h2>
|
||||
<ng-template [ngIf]="!transactions?.length"> </ng-template>
|
||||
<ng-template i18n="X of X Address Transaction" [ngIf]="transactions?.length === 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction</ng-template>
|
||||
<ng-template i18n="X of X Address Transactions (Plural)" [ngIf]="transactions?.length > 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions</ng-template>
|
||||
</h2>
|
||||
<div class="title-tx">
|
||||
<h2>
|
||||
<ng-template [ngIf]="!transactions?.length"> </ng-template>
|
||||
<ng-template i18n="X of X Address Transaction" [ngIf]="transactions?.length === 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction</ng-template>
|
||||
<ng-template i18n="X of X Address Transactions (Plural)" [ngIf]="transactions?.length > 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions</ng-template>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
|
||||
|
||||
@@ -81,7 +90,7 @@
|
||||
|
||||
</ng-template>
|
||||
|
||||
<ng-template [ngIf]="retryLoadmore">
|
||||
<ng-template [ngIf]="retryLoadMore">
|
||||
<br>
|
||||
<button type="button" class="btn btn-outline-info btn-sm" (click)="loadMore()"><fa-icon [icon]="['fas', 'redo-alt']" [fixedWidth]="true"></fa-icon></button>
|
||||
</ng-template>
|
||||
@@ -110,7 +119,7 @@
|
||||
</div>
|
||||
<div class="w-100 d-block d-md-none"></div>
|
||||
<div class="col">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,10 +127,11 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template [ngIf]="error">
|
||||
<br>
|
||||
<div class="text-center">
|
||||
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
||||
<br>
|
||||
<ng-template #displayServerError><i>{{ error.error }}</i></ng-template>
|
||||
<ng-template #displayServerError><i class="small">({{ error.error }})</i></ng-template>
|
||||
<ng-template [ngIf]="error.status === 413 || error.status === 405" [ngIfElse]="displayServerError">
|
||||
<ng-container i18n="Electrum server limit exceeded error">
|
||||
<i>The number of transactions on this address exceeds the Electrum server limit</i>
|
||||
@@ -132,6 +142,8 @@
|
||||
<a href="https://mempool.space/address/{{ addressString }}" target="_blank">https://mempool.space/address/{{ addressString }}</a>
|
||||
<br>
|
||||
<a href="http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}" target="_blank">http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}</a>
|
||||
<br><br>
|
||||
<i class="small">({{ error.error }})</i>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user