Compare commits
367 Commits
v2.2.2-rc1
...
v2.3.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a7533013b | ||
|
|
eeb0f403a3 | ||
|
|
bcbc60b456 | ||
|
|
0d14c30892 | ||
|
|
49c30c7237 | ||
|
|
7ba0055c61 | ||
|
|
72b631a4dc | ||
|
|
b9a2143a5a | ||
|
|
815fb62e7d | ||
|
|
7a19da560e | ||
|
|
43a1af4509 | ||
|
|
cf1471acca | ||
|
|
a2a69e522c | ||
|
|
4939941c88 | ||
|
|
a643f50016 | ||
|
|
836444a1c5 | ||
|
|
24d4b643e5 | ||
|
|
7f045ae5b9 | ||
|
|
96a2c8ab4e | ||
|
|
47103d85d3 | ||
|
|
0af5d733c4 | ||
|
|
bc7bbf5fe5 | ||
|
|
4bc141cd67 | ||
|
|
1c6b3a46c6 | ||
|
|
b41e32915f | ||
|
|
adb5bfe93f | ||
|
|
f4b7bbc91c | ||
|
|
687b4af272 | ||
|
|
e67f552fbd | ||
|
|
28d3f190ff | ||
|
|
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 |
9
.github/workflows/cypress.yml
vendored
9
.github/workflows/cypress.yml
vendored
@@ -15,6 +15,12 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- 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:
|
||||
@@ -25,7 +31,6 @@ jobs:
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
env: BASE_MODULE=mempool
|
||||
group: Tests on ${{ matrix.browser }} (Mempool)
|
||||
browser: ${{ matrix.browser }}
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
@@ -46,7 +51,6 @@ jobs:
|
||||
record: true
|
||||
parallel: true
|
||||
spec: cypress/integration/liquid/liquid.spec.ts
|
||||
env: BASE_MODULE=liquid
|
||||
group: Tests on ${{ matrix.browser }} (Liquid)
|
||||
browser: ${{ matrix.browser }}
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
@@ -67,7 +71,6 @@ jobs:
|
||||
record: true
|
||||
parallel: true
|
||||
spec: cypress/integration/bisq/bisq.spec.ts
|
||||
env: BASE_MODULE=bisq
|
||||
group: Tests on ${{ matrix.browser }} (Bisq)
|
||||
browser: ${{ matrix.browser }}
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
|
||||
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"
|
||||
}
|
||||
40
README.md
40
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:
|
||||
@@ -80,7 +80,7 @@ Install mempool dependencies from npm and build the backend:
|
||||
```bash
|
||||
# backend
|
||||
cd backend
|
||||
npm install
|
||||
npm install --prod
|
||||
npm run build
|
||||
```
|
||||
|
||||
@@ -96,18 +96,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 +116,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 +156,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 +172,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/
|
||||
|
||||
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"
|
||||
}
|
||||
@@ -11,7 +11,9 @@
|
||||
"RECOMMENDED_FEE_PERCENTILE": 50,
|
||||
"BLOCK_WEIGHT_UNITS": 4000000,
|
||||
"INITIAL_BLOCKS_AMOUNT": 8,
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||
"PRICE_FEED_UPDATE_INTERVAL": 3600,
|
||||
"USE_SECOND_NODE_FOR_MINFEE": false
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
@@ -27,8 +29,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.2-dev",
|
||||
"version": "2.3.0-dev",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mempool-backend",
|
||||
"version": "2.2.2-dev",
|
||||
"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.2-dev",
|
||||
"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,7 +6,7 @@ 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 blocks: BlockExtended[] = [];
|
||||
@@ -45,7 +45,7 @@ class Blocks {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -89,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]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
|
||||
import config from '../config';
|
||||
export class Common {
|
||||
static nativeAssetId = '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
|
||||
|
||||
static median(numbers: number[]) {
|
||||
let medianNr = 0;
|
||||
const numsLen = numbers.length;
|
||||
|
||||
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') {
|
||||
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,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();
|
||||
@@ -75,7 +75,8 @@ class MempoolBlocks {
|
||||
let blockSize = 0;
|
||||
let transactions: TransactionExtended[] = [];
|
||||
transactionsSorted.forEach((tx) => {
|
||||
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS || mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT) {
|
||||
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);
|
||||
|
||||
@@ -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,14 @@ import { DB } from '../database';
|
||||
import logger from '../logger';
|
||||
|
||||
import { Statistic, TransactionExtended, OptimizedStatistic } from '../mempool.interfaces';
|
||||
import config from '../config';
|
||||
|
||||
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 +49,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 +85,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 (
|
||||
(config.MEMPOOL.NETWORK === 'liquid' && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
|
||||
||
|
||||
(config.MEMPOOL.NETWORK !== 'liquid' && (i === lastItem || transaction.effectiveFeePerVsize < logFees[i + 1]))
|
||||
) {
|
||||
if (weightVsizeFees[logFees[i]]) {
|
||||
weightVsizeFees[logFees[i]] += transaction.vsize;
|
||||
} else {
|
||||
@@ -255,12 +263,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 +357,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 +394,12 @@ class Statistics {
|
||||
public async $list24H(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(180);
|
||||
const query = this.getQueryForDaysAvg(120, '1 DAY'); // 2m interval
|
||||
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 +407,12 @@ class Statistics {
|
||||
public async $list1W(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(1260);
|
||||
const query = this.getQueryForDaysAvg(600, '1 WEEK'); // 10m 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 +420,12 @@ class Statistics {
|
||||
public async $list1M(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(5040);
|
||||
const query = this.getQueryForDaysAvg(3600, '1 MONTH'); // 1h 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 +433,12 @@ class Statistics {
|
||||
public async $list3M(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(15120);
|
||||
const query = this.getQueryForDaysAvg(14400, '3 MONTH'); // 4h 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 +446,12 @@ class Statistics {
|
||||
public async $list6M(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(30240);
|
||||
const query = this.getQueryForDaysAvg(21600, '6 MONTH'); // 6h 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 +459,42 @@ class Statistics {
|
||||
public async $list1Y(): Promise<OptimizedStatistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(60480);
|
||||
const query = this.getQueryForDays(43200, '1 YEAR'); // 12h 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(86400, "2 YEAR"); // 1d 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(86400, "3 YEAR"); // 1d 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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -14,6 +14,8 @@ interface IConfig {
|
||||
BLOCK_WEIGHT_UNITS: number;
|
||||
INITIAL_BLOCKS_AMOUNT: number;
|
||||
MEMPOOL_BLOCKS_AMOUNT: number;
|
||||
PRICE_FEED_UPDATE_INTERVAL: number;
|
||||
USE_SECOND_NODE_FOR_MINFEE: boolean;
|
||||
};
|
||||
ESPLORA: {
|
||||
REST_API_URL: string;
|
||||
@@ -29,8 +31,7 @@ interface IConfig {
|
||||
USERNAME: string;
|
||||
PASSWORD: string;
|
||||
};
|
||||
CORE_RPC_MINFEE: {
|
||||
ENABLED: boolean;
|
||||
SECOND_CORE_RPC: {
|
||||
HOST: string;
|
||||
PORT: number;
|
||||
USERNAME: string;
|
||||
@@ -75,6 +76,8 @@ const defaults: IConfig = {
|
||||
'BLOCK_WEIGHT_UNITS': 4000000,
|
||||
'INITIAL_BLOCKS_AMOUNT': 8,
|
||||
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
||||
'PRICE_FEED_UPDATE_INTERVAL': 3600,
|
||||
'USE_SECOND_NODE_FOR_MINFEE': false,
|
||||
},
|
||||
'ESPLORA': {
|
||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
@@ -90,8 +93,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',
|
||||
@@ -127,7 +129,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'];
|
||||
@@ -139,7 +141,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,8 @@ 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';
|
||||
|
||||
class Server {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
@@ -68,7 +70,8 @@ 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 });
|
||||
@@ -79,6 +82,11 @@ class Server {
|
||||
|
||||
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) {
|
||||
@@ -111,8 +119,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 +131,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 +149,15 @@ class Server {
|
||||
if (this.wss) {
|
||||
websocketHandler.setWebsocketServer(this.wss);
|
||||
}
|
||||
if (config.MEMPOOL.NETWORK === 'liquid' && 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 +175,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 +224,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 +256,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 +275,12 @@ class Server {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', routes.getAddressPrefix)
|
||||
;
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
|
||||
this.app
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ 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';
|
||||
|
||||
class Routes {
|
||||
constructor() {}
|
||||
@@ -50,12 +52,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 +84,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 +487,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 +501,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 +514,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 +526,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 +536,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 +573,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 +592,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 +607,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 +621,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 +638,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 +654,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 +675,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 +684,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 +693,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,14 +724,16 @@ class Routes {
|
||||
const nextRetargetHeight = blockHeight + remainingBlocks;
|
||||
|
||||
let difficultyChange = 0;
|
||||
if (blocksInEpoch > 0) {
|
||||
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
|
||||
}
|
||||
if (difficultyChange > 300) {
|
||||
difficultyChange = 300;
|
||||
}
|
||||
if (difficultyChange < -75) {
|
||||
difficultyChange = -75;
|
||||
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;
|
||||
@@ -727,7 +748,7 @@ class Routes {
|
||||
const timeAvg = timeAvgMins * 60;
|
||||
const remainingTime = remainingBlocks * timeAvg;
|
||||
const estimatedRetargetDate = remainingTime + now;
|
||||
|
||||
|
||||
const result = {
|
||||
progressPercent,
|
||||
difficultyChange,
|
||||
@@ -737,11 +758,53 @@ class Routes {
|
||||
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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"lib": ["es2019"],
|
||||
"lib": ["es2019", "dom"],
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
|
||||
@@ -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
|
||||
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@@ -34,6 +34,7 @@ speed-measure-plugin.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
|
||||
@@ -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,11 +215,11 @@
|
||||
"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
|
||||
@@ -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,8 +1,8 @@
|
||||
describe('Bisq', () => {
|
||||
let baseModule;
|
||||
const baseModule = Cypress.env("BASE_MODULE");
|
||||
const basePath = (baseModule === 'bisq') ? '' : '/bisq';
|
||||
|
||||
beforeEach(() => {
|
||||
baseModule = (Cypress.env('BASE_MODULE') && Cypress.env('BASE_MODULE') === 'bisq') ? '' : '/bisq';
|
||||
|
||||
cy.intercept('/sockjs-node/info*').as('socket');
|
||||
cy.intercept('/bisq/api/markets/hloc?market=btc_usd&interval=day').as('hloc');
|
||||
cy.intercept('/bisq/api/markets/ticker').as('ticker');
|
||||
@@ -23,15 +23,15 @@ describe('Bisq', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") !== 'liquid') {
|
||||
if (baseModule === 'mempool' || baseModule === 'bisq') {
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit(`${baseModule}`);
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the transactions screen', () => {
|
||||
cy.visit(`${baseModule}`);
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||
cy.get('.table > tr').should('have.length', 50);
|
||||
@@ -39,7 +39,7 @@ describe('Bisq', () => {
|
||||
});
|
||||
|
||||
it('loads the blocks screen', () => {
|
||||
cy.visit(`${baseModule}`);
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait('@blocks');
|
||||
@@ -48,7 +48,7 @@ describe('Bisq', () => {
|
||||
});
|
||||
|
||||
it('loads the stats screen', () => {
|
||||
cy.visit(`${baseModule}`);
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||
cy.wait('@stats');
|
||||
@@ -56,7 +56,7 @@ describe('Bisq', () => {
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
cy.visit(`${baseModule}`);
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||
cy.get('.card').should('have.length.at.least', 1);
|
||||
@@ -67,7 +67,7 @@ describe('Bisq', () => {
|
||||
|
||||
it('shows blocks pagination with 5 pages (desktop)', () => {
|
||||
cy.viewport(760, 800);
|
||||
cy.visit(`${baseModule}/blocks`);
|
||||
cy.visit(`${basePath}/blocks`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('tbody tr').should('have.length', 10);
|
||||
// 5 pages + 4 buttons = 9 buttons
|
||||
@@ -76,13 +76,13 @@ describe('Bisq', () => {
|
||||
|
||||
it('shows blocks pagination with 3 pages (mobile)', () => {
|
||||
cy.viewport(669, 800);
|
||||
cy.visit(`${baseModule}/blocks`);
|
||||
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);
|
||||
});
|
||||
} else {
|
||||
it.skip("Tests cannot be run on the selected BASE_MODULE");
|
||||
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
describe('Liquid', () => {
|
||||
let baseModule;
|
||||
beforeEach(() => {
|
||||
baseModule = (Cypress.env('BASE_MODULE') && Cypress.env('BASE_MODULE') === 'liquid') ? '' : '/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');
|
||||
cy.intercept('/liquid/api/tx/**/outspends').as('outspends');
|
||||
@@ -16,30 +16,36 @@ describe('Liquid', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") !== 'bisq') {
|
||||
if (baseModule === 'mempool' || baseModule === 'liquid') {
|
||||
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#mempool-block-0 > .blockLink').should('exist');
|
||||
});
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit(`${baseModule}`);
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the blocks page', () => {
|
||||
cy.visit(`${baseModule}/blocks`);
|
||||
cy.visit(`${basePath}/blocks`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads a specific block page', () => {
|
||||
cy.visit(`${baseModule}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`);
|
||||
cy.visit(`${basePath}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the graphs page', () => {
|
||||
cy.visit(`${baseModule}/graphs`);
|
||||
cy.visit(`${basePath}/graphs`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the tv page - desktop', () => {
|
||||
cy.visit(`${baseModule}`);
|
||||
cy.visit(`${basePath}`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.wait(1000);
|
||||
@@ -47,7 +53,7 @@ describe('Liquid', () => {
|
||||
});
|
||||
|
||||
it('loads the graphs page - mobile', () => {
|
||||
cy.visit(`${baseModule}`)
|
||||
cy.visit(`${basePath}`)
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||
cy.viewport('iphone-6');
|
||||
@@ -56,15 +62,63 @@ describe('Liquid', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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(`${baseModule}/assets`);
|
||||
cy.visit(`${basePath}/assets`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('table tr').should('have.length.at.least', 5);
|
||||
});
|
||||
|
||||
it('allows searching assets', () => {
|
||||
cy.visit(`${baseModule}/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);
|
||||
@@ -72,7 +126,7 @@ describe('Liquid', () => {
|
||||
});
|
||||
|
||||
it('shows a specific asset ID', () => {
|
||||
cy.visit(`${baseModule}/assets`);
|
||||
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();
|
||||
@@ -84,27 +138,27 @@ describe('Liquid', () => {
|
||||
describe('unblinded TX', () => {
|
||||
|
||||
it('should not show an unblinding error message for regular txs', () => {
|
||||
cy.visit(`${baseModule}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`);
|
||||
cy.visit(`${basePath}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`);
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.error-unblinded' ).should('not.exist');
|
||||
});
|
||||
|
||||
it('show unblinded TX', () => {
|
||||
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
|
||||
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(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=`);
|
||||
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(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123`);
|
||||
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', '');
|
||||
@@ -112,36 +166,36 @@ describe('Liquid', () => {
|
||||
});
|
||||
|
||||
it('show first unblinded vout', () => {
|
||||
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc`);
|
||||
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(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
|
||||
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(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c`);
|
||||
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(`${baseModule}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
|
||||
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(`${baseModule}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`);
|
||||
cy.visit(`${basePath}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`);
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
it.skip("Tests cannot be run on the selected BASE_MODULE");
|
||||
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,13 +41,24 @@ 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');
|
||||
});
|
||||
});
|
||||
|
||||
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
|
||||
if (baseModule === 'mempool') {
|
||||
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('#mempool-block-0 > .blockLink').should('exist');
|
||||
});
|
||||
|
||||
it('loads the status screen', () => {
|
||||
cy.visit('/status');
|
||||
@@ -43,7 +86,7 @@ describe('Mainnet', () => {
|
||||
cy.get('.badge', {timeout: 25000}).should('not.exist');
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
loaded: true
|
||||
command: 'init'
|
||||
}
|
||||
});
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
|
||||
@@ -56,6 +99,77 @@ describe('Mainnet', () => {
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
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', () => {
|
||||
@@ -187,7 +301,7 @@ describe('Mainnet', () => {
|
||||
cy.changeNetwork("bisq");
|
||||
});
|
||||
|
||||
it('loads the dashboard with the skeleton blocks', () => {
|
||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit("/");
|
||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||
@@ -199,7 +313,7 @@ describe('Mainnet', () => {
|
||||
|
||||
emitMempoolInfo({
|
||||
'params': {
|
||||
loaded: true
|
||||
command: 'init'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -224,6 +338,33 @@ describe('Mainnet', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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('/');
|
||||
@@ -241,7 +382,7 @@ describe('Mainnet', () => {
|
||||
cy.visit('/tv');
|
||||
cy.waitForSkeletonGone();
|
||||
cy.get('.chart-holder');
|
||||
cy.get('.blockchain-wrapper').should('be.visible');
|
||||
cy.get('.blockchain-wrapper').should('not.visible');
|
||||
});
|
||||
|
||||
it('loads the api screen', () => {
|
||||
@@ -317,7 +458,79 @@ describe('Mainnet', () => {
|
||||
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");
|
||||
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');
|
||||
@@ -9,13 +11,19 @@ describe('Signet', () => {
|
||||
});
|
||||
|
||||
|
||||
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
|
||||
if (baseModule === 'mempool') {
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/signet');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the dashboard with the skeleton blocks', () => {
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
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');
|
||||
@@ -126,6 +134,6 @@ describe('Signet', () => {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
it.skip("Tests cannot be run on the selected BASE_MODULE");
|
||||
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { confirmAddress, emitMempoolInfo, sendWsMock, showNewTx, startTrackingAddress } from "../../support/websocket";
|
||||
|
||||
const baseModule = Cypress.env("BASE_MODULE");
|
||||
|
||||
describe('Testnet', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('/api/block-height/*').as('block-height');
|
||||
@@ -8,14 +10,20 @@ describe('Testnet', () => {
|
||||
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
|
||||
});
|
||||
|
||||
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
|
||||
if (baseModule === 'mempool') {
|
||||
|
||||
it('loads the dashboard', () => {
|
||||
cy.visit('/testnet');
|
||||
cy.waitForSkeletonGone();
|
||||
});
|
||||
|
||||
it('loads the dashboard with the skeleton blocks', () => {
|
||||
it('check first mempool block after skeleton loads', () => {
|
||||
cy.visit('/');
|
||||
cy.waitForSkeletonGone();
|
||||
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');
|
||||
@@ -123,6 +131,6 @@ describe('Testnet', () => {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
it.skip("Tests cannot be run on the selected BASE_MODULE");
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ const codes = {
|
||||
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(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
25044
frontend/package-lock.json
generated
25044
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.2-dev",
|
||||
"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,7 +30,7 @@
|
||||
"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",
|
||||
@@ -53,34 +53,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",
|
||||
"@juggle/resize-observer": "^3.3.1",
|
||||
"@mempool/mempool.js": "^2.2.4",
|
||||
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
||||
"@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",
|
||||
@@ -88,10 +92,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",
|
||||
@@ -101,21 +103,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ try {
|
||||
throw new Error(e);
|
||||
} else {
|
||||
console.log(`${CONFIG_FILE_NAME} file not found, using default config`);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +60,20 @@ PROXY_CONFIG = [
|
||||
}
|
||||
];
|
||||
|
||||
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,114 +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/v1/": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/liquid/api/": "/api/"
|
||||
}
|
||||
},
|
||||
"/liquid/api/": {
|
||||
"target": "http://localhost:8999/",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/liquid/api/": "/api/v1/"
|
||||
}
|
||||
},
|
||||
"/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,19 +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,
|
||||
@@ -61,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',
|
||||
@@ -95,6 +112,10 @@ let routes: Routes = [
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
@@ -137,9 +158,21 @@ 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'
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -164,6 +197,10 @@ let routes: Routes = [
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
@@ -199,9 +236,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'
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -226,6 +275,10 @@ let routes: Routes = [
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
@@ -261,9 +314,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'
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -325,6 +390,10 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
path: '',
|
||||
component: DashboardComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
@@ -359,9 +428,21 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
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: 'about',
|
||||
@@ -408,3 +489,4 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
||||
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';
|
||||
@@ -26,16 +27,17 @@ import { LiquidMasterPageComponent } from './components/liquid-master-page/liqui
|
||||
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';
|
||||
@@ -45,16 +47,18 @@ 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, 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 } 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 } from '@fortawesome/free-solid-svg-icons';
|
||||
import { ApiDocsComponent } from './components/docs/api-docs.component';
|
||||
import { DocsComponent } from './components/docs/docs.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';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -78,11 +82,12 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
TimeSpanComponent,
|
||||
AddressLabelsComponent,
|
||||
MempoolBlocksComponent,
|
||||
ChartistComponent,
|
||||
FooterComponent,
|
||||
MempoolBlockComponent,
|
||||
FeeDistributionGraphComponent,
|
||||
IncomingTransactionsGraphComponent,
|
||||
MempoolGraphComponent,
|
||||
LbtcPegsGraphComponent,
|
||||
AssetComponent,
|
||||
AssetsComponent,
|
||||
MinerComponent,
|
||||
@@ -95,6 +100,8 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
PrivacyPolicyComponent,
|
||||
TrademarkPolicyComponent,
|
||||
SponsorComponent,
|
||||
PushTransactionComponent,
|
||||
DocsComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
@@ -106,6 +113,9 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
NgbTypeaheadModule,
|
||||
FontAwesomeModule,
|
||||
SharedModule,
|
||||
NgxEchartsModule.forRoot({
|
||||
echarts: () => import('echarts')
|
||||
})
|
||||
],
|
||||
providers: [
|
||||
ElectrsApiService,
|
||||
@@ -134,6 +144,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);
|
||||
@@ -149,5 +160,6 @@ export class AppModule {
|
||||
library.addIcons(faCaretDown);
|
||||
library.addIcons(faAngleRight);
|
||||
library.addIcons(faAngleLeft);
|
||||
library.addIcons(faBook);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -27,7 +27,7 @@
|
||||
<td>{{ asset.ticker }}</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>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<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>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -68,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
<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" />
|
||||
@@ -50,10 +50,14 @@
|
||||
<img class="image" src="/resources/profile/foundry.svg" />
|
||||
<span>Foundry</span>
|
||||
</a>
|
||||
<a href="https://unchained-capital.com/" target="_blank" title="Unchained">
|
||||
<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>
|
||||
|
||||
@@ -126,6 +130,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>
|
||||
@@ -138,10 +146,6 @@
|
||||
<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>
|
||||
|
||||
@@ -159,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_staff">Project Staff</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>
|
||||
|
||||
@@ -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;
|
||||
@@ -37,7 +38,14 @@ 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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -35,17 +35,22 @@
|
||||
h1 {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
margin-right: 10px;
|
||||
font-size: 1.9rem;
|
||||
@media (min-width: 576px) {
|
||||
font-size: 2rem;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.address-link {
|
||||
line-height: 56px;
|
||||
line-height: 56px;
|
||||
margin-left: 0px;
|
||||
top: -2px;
|
||||
position: relative;
|
||||
position: relative;
|
||||
@media (min-width: 768px) {
|
||||
line-height: 69px;
|
||||
}
|
||||
@@ -69,10 +74,20 @@ h1 {
|
||||
|
||||
.tx-link {
|
||||
display: block;
|
||||
width: 100%;
|
||||
top: 14px;
|
||||
height: 100%;
|
||||
top: 9px;
|
||||
position: relative;
|
||||
@media (min-width: 576px) {
|
||||
top: 11px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
top: 20px;
|
||||
top: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-tx {
|
||||
h2 {
|
||||
line-height: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AudioService } from 'src/app/services/audio.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { of, merge, Subscription, Observable } from 'rxjs';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { AddressInformation } from 'src/app/interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-address',
|
||||
@@ -23,15 +24,16 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
isLoadingAddress = true;
|
||||
transactions: Transaction[];
|
||||
isLoadingTransactions = true;
|
||||
retryLoadmore = false;
|
||||
retryLoadMore = false;
|
||||
error: any;
|
||||
mainSubscription: Subscription;
|
||||
addressLoadingStatus$: Observable<number>;
|
||||
addressInfo: null | AddressInformation = null;
|
||||
|
||||
totalConfirmedTxCount = 0;
|
||||
loadedConfirmedTxCount = 0;
|
||||
txCount = 0;
|
||||
receieved = 0;
|
||||
received = 0;
|
||||
sent = 0;
|
||||
|
||||
private tempTransactions: Transaction[];
|
||||
@@ -67,8 +69,12 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
this.address = null;
|
||||
this.isLoadingTransactions = true;
|
||||
this.transactions = null;
|
||||
this.addressInfo = null;
|
||||
document.body.scrollTo(0, 0);
|
||||
this.addressString = params.get('id') || '';
|
||||
if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}$/.test(this.addressString)) {
|
||||
this.addressString = this.addressString.toLowerCase();
|
||||
}
|
||||
this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
|
||||
|
||||
return merge(
|
||||
@@ -92,10 +98,20 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.pipe(
|
||||
filter((address) => !!address),
|
||||
tap((address: Address) => {
|
||||
if (this.stateService.network === 'liquid' && /^([m-zA-HJ-NP-Z1-9]{26,35}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[a-km-zA-HJ-NP-Z1-9]{80})$/.test(address.address)) {
|
||||
this.apiService.validateAddress$(address.address)
|
||||
.subscribe((addressInfo) => {
|
||||
this.addressInfo = addressInfo;
|
||||
this.websocketService.startTrackAddress(addressInfo.unconfidential);
|
||||
});
|
||||
} else {
|
||||
this.websocketService.startTrackAddress(address.address);
|
||||
}
|
||||
}),
|
||||
switchMap((address) => {
|
||||
this.address = address;
|
||||
this.updateChainStats();
|
||||
this.websocketService.startTrackAddress(address.address);
|
||||
this.isLoadingAddress = false;
|
||||
this.isLoadingTransactions = true;
|
||||
return this.electrsApiService.getAddressTransactions$(address.address);
|
||||
@@ -126,7 +142,13 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time;
|
||||
});
|
||||
this.tempTransactions.sort((a, b) => {
|
||||
return b.status.block_time - a.status.block_time || b.firstSeen - a.firstSeen;
|
||||
if (b.status.confirmed) {
|
||||
if (b.status.block_height === a.status.block_height) {
|
||||
return b.status.block_time - a.status.block_time;
|
||||
}
|
||||
return b.status.block_height - a.status.block_height;
|
||||
}
|
||||
return b.firstSeen - a.firstSeen;
|
||||
});
|
||||
|
||||
this.transactions = this.tempTransactions;
|
||||
@@ -161,7 +183,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
transaction.vout.forEach((vout) => {
|
||||
if (vout.scriptpubkey_address === this.address.address) {
|
||||
this.receieved += vout.value;
|
||||
this.received += vout.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -184,7 +206,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
this.isLoadingTransactions = true;
|
||||
this.retryLoadmore = false;
|
||||
this.retryLoadMore = false;
|
||||
this.electrsApiService.getAddressTransactionsFromHash$(this.address.address, this.lastTransactionTxId)
|
||||
.subscribe((transactions: Transaction[]) => {
|
||||
this.lastTransactionTxId = transactions[transactions.length - 1].txid;
|
||||
@@ -194,12 +216,12 @@ export class AddressComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
(error) => {
|
||||
this.isLoadingTransactions = false;
|
||||
this.retryLoadmore = true;
|
||||
this.retryLoadMore = true;
|
||||
});
|
||||
}
|
||||
|
||||
updateChainStats() {
|
||||
this.receieved = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum;
|
||||
this.received = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum;
|
||||
this.sent = this.address.chain_stats.spent_txo_sum + this.address.mempool_stats.spent_txo_sum;
|
||||
this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count;
|
||||
this.totalConfirmedTxCount = this.address.chain_stats.tx_count;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-amount',
|
||||
@@ -8,11 +8,13 @@ import { Observable } from 'rxjs';
|
||||
styleUrls: ['./amount.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AmountComponent implements OnInit {
|
||||
export class AmountComponent implements OnInit, OnDestroy {
|
||||
conversions$: Observable<any>;
|
||||
viewFiat$: Observable<boolean>;
|
||||
network = '';
|
||||
|
||||
stateSubscription: Subscription;
|
||||
|
||||
@Input() satoshis: number;
|
||||
@Input() digitsInfo = '1.8-8';
|
||||
@Input() noFiat = false;
|
||||
@@ -24,7 +26,13 @@ export class AmountComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.viewFiat$ = this.stateService.viewFiat$.asObservable();
|
||||
this.conversions$ = this.stateService.conversions$.asObservable();
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
this.stateSubscription = this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.stateSubscription) {
|
||||
this.stateSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,145 +0,0 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-code-template',
|
||||
templateUrl: './code-template.component.html',
|
||||
styleUrls: ['./code-template.component.scss']
|
||||
})
|
||||
export class CodeTemplateComponent {
|
||||
@Input() network: string;
|
||||
@Input() layer: string;
|
||||
@Input() code: {
|
||||
codeSample: {
|
||||
esModule: string;
|
||||
commonJS: string;
|
||||
curl: string;
|
||||
},
|
||||
responseSample: string;
|
||||
};
|
||||
hostname = document.location.hostname;
|
||||
|
||||
constructor(
|
||||
) { }
|
||||
|
||||
npmGithubLink(){
|
||||
let npmLink = `https://github.com/mempool/mempool.js`;
|
||||
if (this.layer === 'bisq') {
|
||||
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-bisq-js`;
|
||||
}
|
||||
if (this.layer === 'liquid') {
|
||||
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-liquid-js`;
|
||||
}
|
||||
return npmLink;
|
||||
}
|
||||
|
||||
npmModuleLink() {
|
||||
let npmLink = `https://www.npmjs.org/package/@mempool/mempool.js`;
|
||||
if (this.layer === 'bisq') {
|
||||
npmLink = `https://www.npmjs.org/package/@mempool/bisq.js`;
|
||||
}
|
||||
if (this.layer === 'liquid') {
|
||||
npmLink = `https://www.npmjs.org/package/@mempool/liquid.js`;
|
||||
}
|
||||
return npmLink;
|
||||
}
|
||||
|
||||
normalizeCodeHostname(code: string) {
|
||||
let codeText: string;
|
||||
if (this.network === 'bisq' || this.network === 'liquid'){
|
||||
codeText = code.replace('%{1}', this.network);
|
||||
}else{
|
||||
codeText = code.replace('%{1}', 'bitcoin');
|
||||
}
|
||||
return codeText;
|
||||
}
|
||||
|
||||
wrapESmodule(code: string) {
|
||||
let codeText = this.normalizeCodeHostname(code);
|
||||
|
||||
if (this.network && this.network !== 'mainnet') {
|
||||
codeText = codeText.replace('mempoolJS();', `mempoolJS({
|
||||
hostname: '${this.hostname}/${this.network}'
|
||||
});` );
|
||||
}
|
||||
|
||||
let importText = `import mempoolJS from "@mempool/mempool.js";`;
|
||||
if (this.layer === 'bisq') {
|
||||
importText = `import bisqJS from "@mempool/bisq.js";`;
|
||||
}
|
||||
if (this.layer === 'liquid') {
|
||||
importText = `import liquidJS from "@mempool/liquid.js";`;
|
||||
}
|
||||
|
||||
return `${importText}
|
||||
|
||||
const init = async () => {
|
||||
${codeText}
|
||||
};
|
||||
init();`;
|
||||
}
|
||||
|
||||
wrapCommonJS(code: string) {
|
||||
let codeText = this.normalizeCodeHostname(code);
|
||||
|
||||
if (this.network && this.network !== 'mainnet') {
|
||||
codeText = codeText.replace('mempoolJS();', `mempoolJS({
|
||||
hostname: '${this.hostname}/${this.network}'
|
||||
});` );
|
||||
}
|
||||
|
||||
let importText = `<script src="https://mempool.space/mempool.js"></script>`;
|
||||
if (this.layer === 'bisq') {
|
||||
importText = `<script src="https://bisq.markets/bisq.js"></script>`;
|
||||
}
|
||||
if (this.layer === 'liquid') {
|
||||
importText = `<script src="https://liquid.network/liquid.js"></script>`;
|
||||
}
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
${importText}
|
||||
<script>
|
||||
const init = async () => {
|
||||
${codeText}
|
||||
};
|
||||
init();
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>`;
|
||||
}
|
||||
wrapCurl(code: string) {
|
||||
if (this.network && this.network !== 'mainnet') {
|
||||
return code.replace('mempool.space/', `mempool.space/${this.network}/`);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
wrapImportTemplate() {
|
||||
|
||||
let importTemplate = `# npm
|
||||
npm install @mempool/mempool.js --save
|
||||
|
||||
# yarn
|
||||
yarn add @mempool/mempool.js`;
|
||||
|
||||
if (this.layer === 'bisq') {
|
||||
importTemplate = `# npm
|
||||
npm install @mempool/bisq.js --save
|
||||
|
||||
# yarn
|
||||
yarn add @mempool/bisq.js`;
|
||||
}
|
||||
|
||||
if (this.layer === 'liquid') {
|
||||
importTemplate = `# npm
|
||||
npm install @mempool/liquid.js --save
|
||||
|
||||
# yarn
|
||||
yarn add @mempool/liquid.js`;
|
||||
}
|
||||
|
||||
return importTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,7 +39,13 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.router.events.subscribe((val) => {
|
||||
if (val instanceof NavigationEnd) {
|
||||
this.link.setAttribute('href', 'https://mempool.space' + (this.location.path() === '/' ? '' : this.location.path()));
|
||||
let domain = 'mempool.space';
|
||||
if (this.stateService.env.BASE_MODULE === 'liquid') {
|
||||
domain = 'liquid.network';
|
||||
} else if (this.stateService.env.BASE_MODULE === 'bisq') {
|
||||
domain = 'bisq.markets';
|
||||
}
|
||||
this.link.setAttribute('href', 'https://' + domain + this.location.path());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<div class="container-xl">
|
||||
<h1 style="float: left;" i18n="asset|Liquid Asset page title">Asset</h1>
|
||||
<a [routerLink]="['/asset/' | relativeUrl, assetString]" style="line-height: 56px; margin-left: 10px;">
|
||||
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ assetString }}</span>
|
||||
</a>
|
||||
<app-clipboard [text]="assetString"></app-clipboard>
|
||||
<br>
|
||||
<div class="title-asset">
|
||||
<h1 i18n="asset|Liquid Asset page title">Asset</h1>
|
||||
<div class="tx-link">
|
||||
<a [routerLink]="['/asset/' | relativeUrl, assetString]">
|
||||
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ assetString }}</span>
|
||||
</a>
|
||||
<app-clipboard [text]="assetString"></app-clipboard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@@ -72,12 +75,14 @@
|
||||
|
||||
<br>
|
||||
|
||||
<h2>
|
||||
<ng-template [ngIf]="transactions?.length" i18n="asset.M_of_N">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} </ng-template>
|
||||
<ng-template [ngIf]="isNativeAsset" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
|
||||
<ng-template #defaultAsset i18n="Default asset transactions title">Issuance and Burn Transactions</ng-template>
|
||||
</h2>
|
||||
|
||||
<div class="title-tx">
|
||||
<h2>
|
||||
<ng-template [ngIf]="transactions?.length" i18n="asset.M_of_N">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} </ng-template>
|
||||
<ng-template [ngIf]="isNativeAsset" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
|
||||
<ng-template #defaultAsset i18n="Default asset transactions title">Issuance and Burn Transactions</ng-template>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
|
||||
|
||||
<div class="text-center">
|
||||
|
||||
@@ -20,4 +20,33 @@
|
||||
margin-top: 20px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
margin-right: 15px;
|
||||
@media (min-width: 576px) {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-link {
|
||||
display: block;
|
||||
height: 100%;
|
||||
top: 9px;
|
||||
position: relative;
|
||||
@media (min-width: 576px) {
|
||||
top: 11px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
top: 17px;
|
||||
}
|
||||
}
|
||||
.title-tx {
|
||||
h2 {
|
||||
line-height: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,13 @@ export class AssetComponent implements OnInit, OnDestroy {
|
||||
this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time;
|
||||
});
|
||||
this.tempTransactions.sort((a, b) => {
|
||||
return b.status.block_time - a.status.block_time || b.firstSeen - a.firstSeen;
|
||||
if (b.status.confirmed) {
|
||||
if (b.status.block_height === a.status.block_height) {
|
||||
return b.status.block_time - a.status.block_time;
|
||||
}
|
||||
return b.status.block_height - a.status.block_height;
|
||||
}
|
||||
return b.firstSeen - a.firstSeen;
|
||||
});
|
||||
|
||||
this.transactions = this.tempTransactions;
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" [routerLink]="['/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'file-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item mr-2" routerLinkActive="active">
|
||||
<a class="nav-link" [routerLink]="['/api' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cogs']" [fixedWidth]="true" i18n-title="master-page.api" title="API"></fa-icon></a>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" [routerLink]="['/docs']" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
|
||||
</li>
|
||||
<li class="nav-item" routerLinkActive="active">
|
||||
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
<div class="title-block" id="block">
|
||||
<h1>
|
||||
<ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis
|
||||
<div class="next-previous-blocks">
|
||||
<ng-template [ngIf]="blockHeight === 0"><ng-container i18n="@@2303359202781425764">Genesis</ng-container>
|
||||
<span class="next-previous-blocks">
|
||||
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
|
||||
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
@@ -11,10 +11,11 @@
|
||||
<span placement="bottom" class="disable">
|
||||
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="blockHeight" i18n="block.block"> Block
|
||||
<div class="next-previous-blocks">
|
||||
<ng-template [ngIf]="blockHeight" i18n="shared.block-title">Block <ng-container *ngTemplateOutlet="blockTemplateContent"></ng-container></ng-template>
|
||||
<ng-template #blockTemplateContent>
|
||||
<span class="next-previous-blocks">
|
||||
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
|
||||
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
@@ -28,7 +29,7 @@
|
||||
<span *ngIf="!showPreviousBlocklink" placement="bottom" class="disable">
|
||||
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</ng-template>
|
||||
</h1>
|
||||
|
||||
@@ -53,7 +54,7 @@
|
||||
<tr>
|
||||
<td i18n="block.timestamp">Timestamp</td>
|
||||
<td>
|
||||
{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>)</i>
|
||||
</div>
|
||||
@@ -61,11 +62,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="block.size">Size</td>
|
||||
<td [innerHTML]="block.size | bytes: 2"></td>
|
||||
<td [innerHTML]="'‎' + (block.size | bytes: 2)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="block.weight">Weight</td>
|
||||
<td [innerHTML]="block.weight | wuBytes: 2"></td>
|
||||
<td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -168,21 +169,21 @@
|
||||
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
|
||||
</h2>
|
||||
|
||||
<ngb-pagination class="pagination-container" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
||||
<ngb-pagination class="pagination-container float-right" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<app-transactions-list [transactions]="transactions"></app-transactions-list>
|
||||
|
||||
<ng-template [ngIf]="isLoadingTransactions">
|
||||
<div class="text-center mb-4 mt-3">
|
||||
<div class="text-center mb-4" class="tx-skeleton">
|
||||
|
||||
<div class="header-bg box" style="padding: 10px; margin-bottom: 10px;">
|
||||
<div class="header-bg box">
|
||||
<span class="skeleton-loader"></span>
|
||||
</div>
|
||||
|
||||
<div class="header-bg box">
|
||||
<div class="row" style="height: 107px;">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<span class="skeleton-loader"></span>
|
||||
</div>
|
||||
@@ -199,7 +200,7 @@
|
||||
<div class="progress-bar progress-darklight" role="progressbar" [ngStyle]="{'width': txsLoadingStatus + '%' }"></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
<ngb-pagination class="pagination-container float-right" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
||||
@@ -207,7 +208,7 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template [ngIf]="isLoadingBlock && !error">
|
||||
|
||||
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
.fiat {
|
||||
display: block;
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
@media (min-width: 768px) {
|
||||
font-size: 14px;
|
||||
@@ -40,10 +40,7 @@
|
||||
h1 {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
@media (min-width: 576px) {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
line-height: 1;
|
||||
a {
|
||||
&:hover, &:focus{
|
||||
text-decoration: none;;
|
||||
@@ -87,32 +84,23 @@ h1 {
|
||||
}
|
||||
|
||||
.block-tx-title {
|
||||
padding-top: 10px;
|
||||
display: block;
|
||||
text-align: right;
|
||||
margin-top: -30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
margin-top: -15px;
|
||||
position: relative;
|
||||
@media (min-width: 550px) {
|
||||
margin-top: 0px;
|
||||
padding-top: 10px;
|
||||
margin-top: 1rem;
|
||||
flex-direction: row;
|
||||
}
|
||||
h2 {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
line-height: 1.6;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
margin-bottom: -15px;
|
||||
padding-right: 10px;
|
||||
padding-top: 15px;
|
||||
position: relative;
|
||||
top: -22px;
|
||||
width: auto;
|
||||
padding-bottom: 10px;
|
||||
@media (min-width: 550px) {
|
||||
padding-top: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
padding-top: 5px;
|
||||
line-height: 1;
|
||||
padding-bottom: 0px;
|
||||
align-self: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,22 +110,41 @@ h1 {
|
||||
}
|
||||
|
||||
.next-previous-blocks {
|
||||
font-size: 36px;
|
||||
font-size: 28px;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
@media (min-width: 768px) {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1ad8f4;
|
||||
&:hover, &:focus {
|
||||
color: #09a3ba;
|
||||
display: inline-block;
|
||||
// transform: scale(1.2);
|
||||
// transition: 150ms all ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disable {
|
||||
font-size: 36px;
|
||||
font-size: 28px;
|
||||
color: #393e5c73;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-skeleton {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
.header-bg {
|
||||
&:first-child {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
.row {
|
||||
height: 107px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,20 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
map((indicators) => indicators['blocktxs-' + this.blockHash] !== undefined ? indicators['blocktxs-' + this.blockHash] : 0)
|
||||
);
|
||||
|
||||
this.subscription = this.route.paramMap
|
||||
.pipe(
|
||||
this.blocksSubscription = this.stateService.blocks$
|
||||
.subscribe(([block]) => {
|
||||
this.latestBlock = block;
|
||||
this.latestBlocks.unshift(block);
|
||||
this.latestBlocks = this.latestBlocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.setNextAndPreviousBlockLink();
|
||||
|
||||
if (block.id === this.blockHash) {
|
||||
this.block = block;
|
||||
this.fees = block.reward / 100000000 - this.blockSubsidy;
|
||||
}
|
||||
});
|
||||
|
||||
this.subscription = this.route.paramMap.pipe(
|
||||
switchMap((params: ParamMap) => {
|
||||
const blockHash: string = params.get('id') || '';
|
||||
this.block = undefined;
|
||||
@@ -94,7 +106,12 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.isLoadingBlock = true;
|
||||
|
||||
let blockInCache: Block;
|
||||
if (isBlockHeight) {
|
||||
blockInCache = this.latestBlocks.find((block) => block.height === parseInt(blockHash, 10));
|
||||
if (blockInCache) {
|
||||
return of(blockInCache);
|
||||
}
|
||||
return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockHash, 10))
|
||||
.pipe(
|
||||
switchMap((hash) => {
|
||||
@@ -107,12 +124,11 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
this.stateService.blocks$.subscribe(([block]) => {
|
||||
if (block.id === blockHash) {
|
||||
this.block = block;
|
||||
}
|
||||
});
|
||||
|
||||
blockInCache = this.latestBlocks.find((block) => block.id === this.blockHash);
|
||||
if (blockInCache) {
|
||||
return of(blockInCache);
|
||||
}
|
||||
|
||||
return this.electrsApiService.getBlock$(blockHash);
|
||||
}
|
||||
}),
|
||||
@@ -159,14 +175,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.isLoadingBlock = false;
|
||||
});
|
||||
|
||||
this.blocksSubscription = this.stateService.blocks$
|
||||
.subscribe(([block]) => {
|
||||
this.latestBlock = block;
|
||||
this.latestBlocks.unshift(block);
|
||||
this.latestBlocks = this.latestBlocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.setNextAndPreviousBlockLink();
|
||||
});
|
||||
|
||||
this.networkChangedSubscription = this.stateService.networkChanged$
|
||||
.subscribe((network) => this.network = network);
|
||||
|
||||
@@ -269,12 +277,14 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight - 2);
|
||||
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/', block ? block.id : this.block.previousblockhash], { state: { data: { block, blockHeight: this.nextBlockHeight - 2 } } });
|
||||
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/',
|
||||
block ? block.id : this.block.previousblockhash], { state: { data: { block, blockHeight: this.nextBlockHeight - 2 } } });
|
||||
}
|
||||
|
||||
navigateToNextBlock() {
|
||||
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight);
|
||||
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/', block ? block.id : this.nextBlockHeight], { state: { data: { block, blockHeight: this.nextBlockHeight } } });
|
||||
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/',
|
||||
block ? block.id : this.nextBlockHeight], { state: { data: { block, blockHeight: this.nextBlockHeight } } });
|
||||
}
|
||||
|
||||
setNextAndPreviousBlockLink(){
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="blocks-container blockchain-blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
|
||||
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
|
||||
<div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
|
||||
<div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
|
||||
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a>
|
||||
<div class="block-height">
|
||||
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="fee-span">
|
||||
{{ block.feeRange[1] | number:feeRounding }} - {{ block.feeRange[block.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
</div>
|
||||
<div class="block-size" [innerHTML]="block.size | bytes: 2">‎</div>
|
||||
<div class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
|
||||
<div class="transaction-count">
|
||||
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
|
||||
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
||||
@@ -25,7 +25,7 @@
|
||||
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="transition" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
|
||||
</div>
|
||||
|
||||
<ng-template #loadingBlocksTemplate >
|
||||
<ng-template #loadingBlocksTemplate>
|
||||
<div class="blocks-container">
|
||||
<div class="flashing">
|
||||
<div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn" >
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
|
||||
.flashing {
|
||||
animation: opacityPulse 2s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -119,4 +119,4 @@
|
||||
0% {opacity: 0.7;}
|
||||
50% {opacity: 1.0;}
|
||||
100% {opacity: 0.7;}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Block } from 'src/app/interfaces/electrs.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { specialBlocks } from 'src/app/app.constants';
|
||||
|
||||
@Component({
|
||||
selector: 'app-blockchain-blocks',
|
||||
@@ -11,7 +12,7 @@ import { Router } from '@angular/router';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
|
||||
specialBlocks = specialBlocks;
|
||||
network = '';
|
||||
blocks: Block[] = [];
|
||||
emptyBlocks: Block[] = this.mountEmptyBlocks();
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.btn-link {
|
||||
padding: 0.25rem 0 0.1rem 0.5rem;
|
||||
}
|
||||
|
||||
880
frontend/src/app/components/docs/api-docs.component.html
Normal file
880
frontend/src/app/components/docs/api-docs.component.html
Normal file
@@ -0,0 +1,880 @@
|
||||
<ng-container *ngIf="{ val: network$ | async } as network">
|
||||
<div class="container-xl text-left">
|
||||
|
||||
<div id="restAPI" *ngIf="restTabActivated">
|
||||
|
||||
<p>Reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">API service</ng-container>.</p>
|
||||
|
||||
<div class="api-category" *ngIf="network.val !== 'bisq' && network.val !== 'liquid'">
|
||||
<h4 i18n="api-docs.tab.general|API Docs tab for General">General</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark">
|
||||
|
||||
<ngb-panel id="difficultyAdjustment" *ngIf="network.val !== 'liquid'">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Difficulty Adjustment</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="difficulty">
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.difficulty)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/difficulty-adjustment</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns details about difficulty adjustment.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.difficulty" [network]="network.val" ></app-code-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category" *ngIf="network.val === 'bisq'">
|
||||
<h4 i18n="Bisq All Markets">Markets</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
|
||||
|
||||
<ngb-panel id="bisqMarketsCurrencies">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Market Currencies</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.marketsCurrencies)" target="_blank">GET {{ baseNetworkUrl }}/api/currencies</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides list of available currencies for a given base currency. </div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.marketsCurrencies" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="marketDepth">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Market Depth</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.marketDepth)" target="_blank">GET {{ baseNetworkUrl }}/api/depth?market=[:market]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides list of open offer prices for a single market.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.marketDepth" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="bisqMarketsHloc">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Market HLOC</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.marketHloc)" target="_blank">GET {{ baseNetworkUrl }}/api/hloc?market=[:market]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides hi/low/open/close data for a given market. This can be used to generate a candlestick chart.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.marketHloc" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="bisqMarketsMarkets">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Markets</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.markets)" target="_blank">GET {{ baseNetworkUrl }}/api/markets</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides list of available markets.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.markets" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="bisqMarketsOffers">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Market Offers</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.marketOffers)" target="_blank">GET {{ baseNetworkUrl }}/api/offers?market=[:market]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides list of open offer details for a single market.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.marketOffers" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="bisqMarketsTicker">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Market Ticker</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.marketTicker)" target="_blank">GET {{ baseNetworkUrl }}/api/ticker?market=[:market]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides 24 hour price ticker for single market or all markets</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.marketTicker" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="bisqMarketsTrades">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Market Trades</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.marketTrades)" target="_blank">GET {{ baseNetworkUrl }}/api/trades?market=[:market]&limit=[:limit]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides list of completed trades for a single market.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.marketTrades" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="bisqMarketsVolumes">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Market Volumes</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.marketVolumes)" target="_blank">GET {{ baseNetworkUrl }}/api/volumes?basecurrency=[:basecurrency]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Provides periodic volume data in terms of base currency for one or all markets.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.marketVolumes" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category" *ngIf="network.val === 'bisq'">
|
||||
<h4 ngbNavLink i18n="api-docs.tab.bsq|API Docs tab for BSQ">General</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
|
||||
|
||||
<ngb-panel id="bisqStats">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Stats</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.stats)" target="_blank'" target="_blank">GET {{ baseNetworkUrl }}/api/stats</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns statistics about all Bisq transactions.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.stats" [network]="network.val"></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category">
|
||||
<h4 i18n="api-docs.tab.addresses|API Docs tab for Addresses">Addresses</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
|
||||
|
||||
<ngb-panel id="address">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Address</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.address)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns details about an address. Available fields: <code>address</code>, <code>chain_stats</code>, and <code>mempool_stats</code>. {{ '{' }}chain,mempool{{ '}' }}_stats each contain an object with <code>tx_count</code>, <code>funded_txo_count</code>, <code>funded_txo_sum</code>, <code>spent_txo_count</code>, and <code>spent_txo_sum</code>.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.address" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressTransactions">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Address Transactions</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.addressTransactions)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using <code>:last_seen_txid</code> (see below).</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.addressTransactions" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressTransactionsChain">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Address Transactions Chain</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.addressTransactionsChain)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs/chain</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.addressTransactionsChain" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressTransactionsMempool">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Address Transactions Mempool</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.addressTransactionsMempool)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs/mempool</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Get unconfirmed transaction history for the specified address/scripthash. Returns up to 50 transactions (no paging).</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.addressTransactionsMempool" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressUTXO">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Address UTXO</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.addressUTXO)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/utxo</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: <code>txid</code>, <code>vout</code>, <code>value</code>, and <code>status</code> (with the status of the funding tx).<ng-container *ngIf="network.val === 'liquid'">There is also a <code>valuecommitment</code> field that may appear in place of <code>value</code>, plus the following additional fields: <code>asset</code>/<code>assetcommitment</code>, <code>nonce</code>/<code>noncecommitment</code>, <code>surjection_proof</code>, and <code>range_proof</code>.</ng-container></div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.addressUTXO" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category" *ngIf="network.val === 'liquid'">
|
||||
<h4 i18n="api-docs.tab.assets|API Docs tab for Assets">Assets</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
|
||||
|
||||
<ngb-panel id="assets">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Assets</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.assets)" target="_blank">GET /liquid/api/asset/:asset_id</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns information about a Liquid asset.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.assets" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="assetTransactions">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Asset Transactions</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<a [href]="wrapUrl(network.val, code.assetTransactions)" target="_blank">GET /liquid/api/asset/:asset_id/txs[/mempool|/chain]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns transactions associated with the specified Liquid asset. For the network's native asset, returns a list of peg in, peg out, and burn transactions. For user-issued assets, returns a list of issuance, reissuance, and burn transactions. Does not include regular transactions transferring this asset.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.assetTransactions" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="assetSupply">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Asset Supply</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.assetSupply)" target="_blank">GET /liquid/api/asset/:asset_id/supply[/decimal]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Get the current total supply of the specified asset. For the native asset (L-BTC), this is calculated as [chain,mempool]_stats.peg_in_amount - [chain,mempool]_stats.peg_out_amount - [chain,mempool]_stats.burned_amount. For issued assets, this is calculated as [chain,mempool]_stats.issued_amount - [chain,mempool]_stats.burned_amount. Not available for assets with blinded issuances. If /decimal is specified, returns the supply as a decimal according to the asset's divisibility. Otherwise, returned in base units.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.assetSupply" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category">
|
||||
<h4 i18n="api-docs.tab.blocks|API Docs tab for Blocks">Blocks</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
|
||||
|
||||
<ngb-panel id="block">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.block)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns details about a block. Available fields: <code>id</code>, <code>height</code>, <code>version</code>, <code>timestamp</code>, <code>bits</code>, <code>nonce</code>, <code>merkle_root</code>, <code>tx_count</code>, <code>size</code>, <code>weight</code>,<ng-container *ngIf="network.val === 'liquid'"> <code>proof</code>,</ng-container> and <code>previousblockhash</code>.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.block" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockHeader">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Header</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockHeader)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/header</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the hex-encoded block header.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockHeader" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockHeight">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Height</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockHeader)" target="_blank">GET {{ baseNetworkUrl }}/api/block-height/:height</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the hash of the block currently at <code>:height</code>.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockHeight" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockRaw">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Raw</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<a [href]="wrapUrl(network.val, code.blockRaw)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/raw</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the raw block representation in binary.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockRaw" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockStatus">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Status</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="title">Get Block Status</div>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockStatus)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/status</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the confirmation status of a block. Available fields: <code>in_best_chain</code> (boolean, false for orphaned blocks), <code>next_best</code> (the hash of the next block, only available for blocks in the best chain).</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockStatus" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="blockTipHeight">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Tip Height</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockTipHeight)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/tip/height</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the height of the last block.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockTipHeight" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTipHash">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Tip Hash</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockTipHash)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/tip/hash</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the hash of the last block.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockTipHash" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTxId">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Transaction ID</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockTxId)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txid/:index</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the transaction at index <code>:index</code> within the specified block.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockTxId" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTxIds">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Transaction IDs</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockTxIds)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txids</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns a list of all txids in the block.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockTxIds" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTxs">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Block Transactions</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blockTxs)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txs[/:start_index]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns a list of transactions in the block (up to 25 transactions beginning at <code>start_index</code>). Transactions returned here do not have the <code>status</code> field, since all the transactions share the same block and confirmation status.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blockTxs" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="blocks">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Blocks</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blocks)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks[/:start_height]</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blocks" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val === 'bisq'" id="blocks">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Blocks</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.blocksBisq)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/:index/:length</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.blocksBisq" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category" *ngIf="network.val !== 'bisq'">
|
||||
<h4 i18n="api-docs.tab.fees|API Docs tab for Fees">Fees</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark">
|
||||
|
||||
<ngb-panel id="feeMempoolBlocks">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Mempool Blocks Fees</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.feeMempoolBlocks)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/fees/mempool-blocks</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.fees.mempool-blocks|API Docs for /api/v1/fees/mempool-blocks">Returns current mempool as projected blocks.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.feeMempoolBlocks" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="feeRecommended">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Recommended Fees</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.feeRecommended)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/fees/recommended</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.fees.recommended|API Docs for /api/v1/fees/recommended">Returns our currently suggested fees for new transactions.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.feeRecommended" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category" *ngIf="network.val !== 'bisq'">
|
||||
<h4 i18n="api-docs.tab.mempool|API Docs tab for Mempool">Mempool</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
|
||||
|
||||
<ngb-panel id="mempool">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Mempool</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.mempool)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.mempool.mempool|API Docs for /api/mempool">Returns current mempool backlog statistics.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.mempool" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="mempoolTxs">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Mempool Transactions IDs</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.mempoolTxs)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool/txids</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.mempool.txids|API Docs for /api/mempool/txids">Get the full list of txids in the mempool as an array. The order of the txids is arbitrary and does not match bitcoind.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.mempoolTxs" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="mempoolRecent">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Mempool Recent</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.mempoolRecent)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool/recent</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.mempool.recent|API Docs for /api/mempool/recent">Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: <code>txid</code>, <code>fee</code>, <code>vsize</code>, and <code>value</code>.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.mempoolRecent" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
|
||||
<div class="api-category">
|
||||
<h4 i18n="api-docs.tab.transactions|API Docs tab for Transactions">Transactions</h4>
|
||||
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="cpfp">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Children Pay for Parent</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionCpfp)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/cpfp/:txid</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.fees.cpfp|API Docs for /api/v1/fees/cpfp">Returns the ancestors and the best descendant fees for a transaction.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionCpfp" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="transaction">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<a [href]="wrapUrl(network.val, code.transaction)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns details about a transaction. Available fields: <code>txid</code>, <code>version</code>, <code>locktime</code>, <code>size</code>, <code>weight</code>, <code>fee</code>, <code>vin</code>, <code>vout</code>, and <code>status</code>.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transaction" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionHex">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction Hex</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionHex)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/hex</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns a transaction serialized as hex.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionHex" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq' && network.val !== 'liquid'" id="transactionMerkleBlockProof">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction Merkleblock Proof</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionMerkleBlockProof)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/merkleblock-proof</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns a merkle inclusion proof for the transaction using <a href="https://bitcoin.org/en/glossary/merkle-block">bitcoind's merkleblock</a> format.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionMerkleBlockProof" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionMerkleProof">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction Merkle Proof</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionMerkleProof)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/merkle-proof</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns a merkle inclusion proof for the transaction using <a href="https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle">Electrum's blockchain.transaction.get_merkle format.</a></div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionMerkleProof" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionOutspend">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction Outspend</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionOutspend)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/outspend/:vout</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the spending status of a transaction output. Available fields: <code>spent</code> (boolean), <code>txid</code> (optional), <code>vin</code> (optional), and <code>status</code> (optional, the status of the spending tx).</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionOutspend" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionOutspends">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction Outspends</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionOutspends)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/outspends</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the spending status of all transaction outputs.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionOutspends" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionRaw">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction Raw</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionRaw)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/raw</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns a transaction as binary data.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionRaw" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionStatus">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transaction Status</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionStatus)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/status</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns the confirmation status of a transaction. Available fields: <code>confirmed</code> (boolean), <code>block_height</code> (optional), and <code>block_hash</code> (optional).</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionStatus" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val === 'bisq'" id="transactionsBisq">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>GET Transactions</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="title">Get Mempool Txids</div>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<a [href]="wrapUrl(network.val, code.transactionsBisq)" target="_blank">GET {{ baseNetworkUrl }}/api/txs/:index/:length</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Returns :length of latest Bisq transactions, starting from :index.</div>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [code]="code.transactionsBisq" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel *ngIf="network.val !== 'bisq'" id="postTransaction">
|
||||
<ng-template ngbPanelTitle>
|
||||
<span>POST Transaction</span>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
<div>POST /api/tx</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n>Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The <code>txid</code> will be returned on success.</div>
|
||||
</div>
|
||||
<app-code-template [method]="'post'" [hostname]="hostname" [code]="code.transactionPost" [network]="network.val" ></app-code-template>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
</ngb-accordion>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="websocketAPI" *ngIf="!restTabActivated && ( network.val !== 'bisq' )">
|
||||
<div class="api-category">
|
||||
<div class="websocket">
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
{{ wrapUrl(network.val, code.websocket, true) }}
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.websocket.websocket">Default push: <code>{{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }}</code> to express what you want pushed. Available: <code>blocks</code>, <code>mempool-blocks</code>, <code>live-2h-chart</code>, and <code>stats</code>.<br><br>Push transactions related to address: <code>{{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }}</code> to receive all new transactions containing that address as input or output. Returns an array of transactions. <code>address-transactions</code> for new mempool transactions, and <code>block-transactions</code> for new block confirmed transactions.</div>
|
||||
</div>
|
||||
<app-code-template [method]="'websocket'" [hostname]="hostname" [code]="code.websocket" [network]="network.val" ></app-code-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="text-center">
|
||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||
|
|
||||
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -29,7 +29,7 @@ li.nav-item {
|
||||
}
|
||||
}
|
||||
|
||||
.code-tab {
|
||||
.code-tab {
|
||||
width: auto;
|
||||
margin: 20px auto 10px;
|
||||
li.nav-item {
|
||||
@@ -67,10 +67,15 @@ li.nav-item {
|
||||
height: 1px;
|
||||
background: #333;
|
||||
}
|
||||
.websocket {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.difficulty {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#restAPI .api-category {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.api-category h4 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
3675
frontend/src/app/components/docs/api-docs.component.ts
Normal file
3675
frontend/src/app/components/docs/api-docs.component.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,20 @@
|
||||
<div class="code">
|
||||
<ul ngbNav #navCodeTemplate="ngbNav" class="nav-tabs code-tab">
|
||||
<li ngbNavItem *ngIf="code.codeSample.curl">
|
||||
<li ngbNavItem *ngIf="code.codeTemplate.curl && method !== 'websocket'">
|
||||
<a ngbNavLink>cURL</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapCurl(code.codeSample.curl)"></app-clipboard></div>
|
||||
<pre><code [innerText]="wrapCurl(code.codeSample.curl)"></code></pre>
|
||||
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapCurlTemplate(code)"></app-clipboard></div>
|
||||
<pre><code [innerText]="wrapCurlTemplate(code)"></code></pre>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem>
|
||||
<a ngbNavLink>CommonJS</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapCommonJS(code.codeSample.commonJS)"></app-clipboard></div>
|
||||
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapCommonJS(code)"></app-clipboard></div>
|
||||
<div class="links">
|
||||
<a [href]="npmGithubLink()" target="_blank">github repository</a>
|
||||
</div>
|
||||
<pre><code [innerText]="wrapCommonJS(code.codeSample.commonJS)"></code></pre>
|
||||
<pre><code [innerText]="wrapCommonJS(code)"></code></pre>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li ngbNavItem>
|
||||
@@ -26,14 +26,14 @@
|
||||
<a [href]="npmModuleLink()" target="_blank">npm package</a>
|
||||
</div>
|
||||
<pre><code [innerText]="wrapImportTemplate()"></code></pre>
|
||||
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapESmodule(code.codeSample.esModule)"></app-clipboard></div>
|
||||
<pre><code [innerText]="wrapESmodule(code.codeSample.esModule)"></code></pre>
|
||||
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapEsModule(code)"></app-clipboard></div>
|
||||
<pre><code [innerText]="wrapEsModule(code)"></code></pre>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="navCodeTemplate"></div>
|
||||
<div *ngIf="code.responseSample" class="response">
|
||||
<div class="subtitle"><ng-container i18n="API Docs API response">Response</ng-container> <app-clipboard [text]="code.responseSample"></app-clipboard></div>
|
||||
<pre><code [innerText]="code.responseSample"></code></pre>
|
||||
<div *ngIf="code.codeTemplate && wrapResponse(code) !== ''" class="response">
|
||||
<div class="subtitle"><ng-container i18n="API Docs API response">Response</ng-container> <app-clipboard [text]="wrapResponse(code)"></app-clipboard></div>
|
||||
<pre><code [innerText]="wrapResponse(code)"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user