Compare commits
175 Commits
v2.2.0-dev
...
v2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb6db6caf3 | ||
|
|
78c44eedbc | ||
|
|
b48a48a6be | ||
|
|
8e1aae1bbf | ||
|
|
807d4b0327 | ||
|
|
df588695ec | ||
|
|
da13349b14 | ||
|
|
6be733490f | ||
|
|
fdf15c39a6 | ||
|
|
3b020046b7 | ||
|
|
8574ee6edd | ||
|
|
f937ea5745 | ||
|
|
741a020579 | ||
|
|
33d37a9b5b | ||
|
|
446bdfebea | ||
|
|
ca91afe45b | ||
|
|
33a5be5a7d | ||
|
|
6a4eee3711 | ||
|
|
13931ceec6 | ||
|
|
0c418a9e33 | ||
|
|
6f8b95a17f | ||
|
|
389c1d794c | ||
|
|
fca66f1b9f | ||
|
|
4c7d0cd2e5 | ||
|
|
1016586992 | ||
|
|
38c8f3acb4 | ||
|
|
962023fbc4 | ||
|
|
b4f8bb2f48 | ||
|
|
c26461fada | ||
|
|
1a996e1640 | ||
|
|
c80532b420 | ||
|
|
74c49b9ae7 | ||
|
|
3f03c9c2b6 | ||
|
|
f00e727e68 | ||
|
|
4338dd6c3f | ||
|
|
8385c50605 | ||
|
|
93c4b1caf1 | ||
|
|
49810b6a47 | ||
|
|
28d685a661 | ||
|
|
95d3d0feaf | ||
|
|
cbc5d67f62 | ||
|
|
87575bc0a2 | ||
|
|
8f74ef58f8 | ||
|
|
2475c67d5b | ||
|
|
bf45bf7b39 | ||
|
|
a1f0417997 | ||
|
|
237f265aab | ||
|
|
0087700aa5 | ||
|
|
861344ed6d | ||
|
|
9e343b346a | ||
|
|
e857dbc874 | ||
|
|
a10cd09ba8 | ||
|
|
f30777934f | ||
|
|
4f6bf297bf | ||
|
|
0121052f0b | ||
|
|
1bd0c40c15 | ||
|
|
2ee96cae44 | ||
|
|
28c8d7dba0 | ||
|
|
9b05ecedc6 | ||
|
|
8fbd273733 | ||
|
|
dec8ae2930 | ||
|
|
353b0e8729 | ||
|
|
71bfcea8a6 | ||
|
|
c54c30209e | ||
|
|
abc6b1519e | ||
|
|
4dcda2cf47 | ||
|
|
d055fabfeb | ||
|
|
dbb365f5e3 | ||
|
|
efb5deda43 | ||
|
|
a4cd6450e3 | ||
|
|
edad15da0d | ||
|
|
e70fd0045d | ||
|
|
794bc99cb6 | ||
|
|
cd1ec53af0 | ||
|
|
3e435d1394 | ||
|
|
50b94f8b72 | ||
|
|
f6f5b69487 | ||
|
|
66b27b9dd0 | ||
|
|
71fa2d67cb | ||
|
|
5cd2cfa097 | ||
|
|
cfd13b3655 | ||
|
|
3ffa60db1f | ||
|
|
4442964124 | ||
|
|
cb034020ef | ||
|
|
5aa57d6df9 | ||
|
|
c1a79e3a33 | ||
|
|
bbd21c9401 | ||
|
|
ad22f9cb46 | ||
|
|
939955fb84 | ||
|
|
63e67dba38 | ||
|
|
8a1230623e | ||
|
|
f20c73af7b | ||
|
|
12c99b86b7 | ||
|
|
934dd67384 | ||
|
|
870bd54b38 | ||
|
|
89300dae98 | ||
|
|
482a891cec | ||
|
|
098ab7d3a7 | ||
|
|
147d44d14b | ||
|
|
8ccdf3973c | ||
|
|
c09eb651ef | ||
|
|
ac91d814d6 | ||
|
|
be2f024da1 | ||
|
|
f137f45cef | ||
|
|
90784deacc | ||
|
|
8ed664e3a9 | ||
|
|
17b6916f31 | ||
|
|
b778d96910 | ||
|
|
5b2eb16d1c | ||
|
|
af61357ced | ||
|
|
f281e84396 | ||
|
|
0dc255edf9 | ||
|
|
2f8f3ca2e9 | ||
|
|
39bb93970b | ||
|
|
72d01a0b67 | ||
|
|
0b4da88802 | ||
|
|
d2fe000ad0 | ||
|
|
dcedc8a5ff | ||
|
|
0d03a9e6cc | ||
|
|
24b7acdc60 | ||
|
|
1000f4dd4d | ||
|
|
d5dba9128e | ||
|
|
84b0375c0c | ||
|
|
bf23a6649c | ||
|
|
aea35d4c86 | ||
|
|
52b7efdd53 | ||
|
|
492abad7a6 | ||
|
|
f566eae471 | ||
|
|
2f2be5c64b | ||
|
|
5d1af0a86e | ||
|
|
5cd5280b21 | ||
|
|
3a957ece05 | ||
|
|
3ead05fa51 | ||
|
|
8a838cd4dc | ||
|
|
b05f731332 | ||
|
|
06fd821bf8 | ||
|
|
6dbfcc9d1a | ||
|
|
001bddd529 | ||
|
|
56518b9655 | ||
|
|
da050ee3dc | ||
|
|
5878a2e631 | ||
|
|
c1fc08196b | ||
|
|
95a80157a7 | ||
|
|
165aa6eee2 | ||
|
|
b8fe7b621c | ||
|
|
04ec5e9564 | ||
|
|
2d4dff6de8 | ||
|
|
5cb98b9813 | ||
|
|
d4508bd876 | ||
|
|
6ccac1df79 | ||
|
|
b38fc824e6 | ||
|
|
cdbe90c182 | ||
|
|
6b5b80f866 | ||
|
|
d74677628b | ||
|
|
f0d46d6ed8 | ||
|
|
220d9afd97 | ||
|
|
dfd88a7ff9 | ||
|
|
eec36ae4e6 | ||
|
|
0a07a16650 | ||
|
|
e62ee72149 | ||
|
|
117f5410d7 | ||
|
|
f6ea45b61f | ||
|
|
344d1247bd | ||
|
|
fcf7955d63 | ||
|
|
1ae002385d | ||
|
|
dc36bfcfe4 | ||
|
|
da77dbece1 | ||
|
|
8e29a4cefd | ||
|
|
146fcfc16d | ||
|
|
308dd2c7ad | ||
|
|
1d4ed85d50 | ||
|
|
d99fd5d59a | ||
|
|
2fca34faaa | ||
|
|
38e866995f | ||
|
|
eeb7447988 |
15
.github/workflows/on-tag.yml
vendored
15
.github/workflows/on-tag.yml
vendored
@@ -28,6 +28,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
printf " TAG: %s\n" "$TAG"
|
printf " TAG: %s\n" "$TAG"
|
||||||
|
|
||||||
|
- name: Add SHORT_SHA env property with commit short sha
|
||||||
|
run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Login to Docker for building
|
- name: Login to Docker for building
|
||||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||||
|
|
||||||
@@ -64,14 +67,6 @@ jobs:
|
|||||||
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
|
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
|
||||||
--output "type=registry" ./${{ matrix.service }}/
|
|
||||||
|
|
||||||
- name: Run Docker buildx for ${{ matrix.service }} against latest
|
|
||||||
run: |
|
|
||||||
docker buildx build \
|
|
||||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
|
||||||
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
|
||||||
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
|
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
|
||||||
--output "type=registry" ./${{ matrix.service }}/
|
--output "type=registry" ./${{ matrix.service }}/ \
|
||||||
|
--build-arg commitHash=$SHORT_SHA
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
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.
|
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
|
## Installation Methods
|
||||||
|
|
||||||
|
|||||||
@@ -50,11 +50,7 @@
|
|||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"TX_PER_SECOND_SAMPLE_PERIOD": 150
|
"TX_PER_SECOND_SAMPLE_PERIOD": 150
|
||||||
},
|
},
|
||||||
"BISQ_BLOCKS": {
|
"BISQ": {
|
||||||
"ENABLED": false,
|
|
||||||
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db/json"
|
|
||||||
},
|
|
||||||
"BISQ_MARKETS": {
|
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
|
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
|
||||||
}
|
}
|
||||||
|
|||||||
99
backend/package-lock.json
generated
99
backend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mempool-backend",
|
"name": "mempool-backend",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mempool-backend",
|
"name": "mempool-backend",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"license": "GNU Affero General Public License v3.0",
|
"license": "GNU Affero General Public License v3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mempool/bitcoin": "^3.0.2",
|
"@mempool/bitcoin": "^3.0.2",
|
||||||
@@ -18,13 +18,13 @@
|
|||||||
"locutus": "^2.0.12",
|
"locutus": "^2.0.12",
|
||||||
"mysql2": "2.2.5",
|
"mysql2": "2.2.5",
|
||||||
"node-worker-threads-pool": "^1.4.3",
|
"node-worker-threads-pool": "^1.4.3",
|
||||||
"ws": "^7.4.4"
|
"ws": "^7.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.0.1",
|
"@types/compression": "^1.0.1",
|
||||||
"@types/express": "^4.17.2",
|
"@types/express": "^4.17.2",
|
||||||
"@types/locutus": "^0.0.6",
|
"@types/locutus": "^0.0.6",
|
||||||
"@types/ws": "^6.0.4",
|
"@types/ws": "^7.4.4",
|
||||||
"tslint": "^6.1.0",
|
"tslint": "^6.1.0",
|
||||||
"typescript": "^4.1.5"
|
"typescript": "^4.1.5"
|
||||||
}
|
}
|
||||||
@@ -163,10 +163,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "6.0.4",
|
"version": "7.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz",
|
||||||
"integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==",
|
"integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@@ -548,17 +549,17 @@
|
|||||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||||
},
|
},
|
||||||
"node_modules/elliptic": {
|
"node_modules/elliptic": {
|
||||||
"version": "6.5.3",
|
"version": "6.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.11.9",
|
||||||
"brorand": "^1.0.1",
|
"brorand": "^1.1.0",
|
||||||
"hash.js": "^1.0.0",
|
"hash.js": "^1.0.0",
|
||||||
"hmac-drbg": "^1.0.0",
|
"hmac-drbg": "^1.0.1",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.4",
|
||||||
"minimalistic-assert": "^1.0.0",
|
"minimalistic-assert": "^1.0.1",
|
||||||
"minimalistic-crypto-utils": "^1.0.0"
|
"minimalistic-crypto-utils": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
@@ -569,11 +570,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es6-promise": {
|
|
||||||
"version": "4.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
|
||||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
|
||||||
},
|
|
||||||
"node_modules/escape-html": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@@ -895,14 +891,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/locutus": {
|
"node_modules/locutus": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.15.tgz",
|
||||||
"integrity": "sha512-0H1o1iHNEp3kJ5rW57bT/CAP5g6Qm0Zd817Wcx2+rOMTYyIJoc482Ja1v9dB6IUjwvWKcBNdYi7x2lRXtlJ3bA==",
|
"integrity": "sha512-2xWC4RkoAoCVXEb/stzEgG1TNgd+mrkLBj6TuEDNyUoKeQ2XzDTyJUC23sMiqbL6zJmJSP3w59OZo+zc4IBOmA==",
|
||||||
"dependencies": {
|
|
||||||
"es6-promise": "^4.2.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.12.0"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/long": {
|
"node_modules/long": {
|
||||||
@@ -1544,9 +1537,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "7.4.4",
|
"version": "7.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||||
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
|
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.3.0"
|
"node": ">=8.3.0"
|
||||||
},
|
},
|
||||||
@@ -1698,9 +1691,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/ws": {
|
"@types/ws": {
|
||||||
"version": "6.0.4",
|
"version": "7.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz",
|
||||||
"integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==",
|
"integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
@@ -2042,17 +2035,17 @@
|
|||||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.3",
|
"version": "6.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.11.9",
|
||||||
"brorand": "^1.0.1",
|
"brorand": "^1.1.0",
|
||||||
"hash.js": "^1.0.0",
|
"hash.js": "^1.0.0",
|
||||||
"hmac-drbg": "^1.0.0",
|
"hmac-drbg": "^1.0.1",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.4",
|
||||||
"minimalistic-assert": "^1.0.0",
|
"minimalistic-assert": "^1.0.1",
|
||||||
"minimalistic-crypto-utils": "^1.0.0"
|
"minimalistic-crypto-utils": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"encodeurl": {
|
"encodeurl": {
|
||||||
@@ -2060,11 +2053,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||||
},
|
},
|
||||||
"es6-promise": {
|
|
||||||
"version": "4.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
|
||||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
|
||||||
},
|
|
||||||
"escape-html": {
|
"escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@@ -2321,12 +2309,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"locutus": {
|
"locutus": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.15.tgz",
|
||||||
"integrity": "sha512-0H1o1iHNEp3kJ5rW57bT/CAP5g6Qm0Zd817Wcx2+rOMTYyIJoc482Ja1v9dB6IUjwvWKcBNdYi7x2lRXtlJ3bA==",
|
"integrity": "sha512-2xWC4RkoAoCVXEb/stzEgG1TNgd+mrkLBj6TuEDNyUoKeQ2XzDTyJUC23sMiqbL6zJmJSP3w59OZo+zc4IBOmA=="
|
||||||
"requires": {
|
|
||||||
"es6-promise": "^4.2.5"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"long": {
|
"long": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -2833,9 +2818,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "7.4.4",
|
"version": "7.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||||
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
|
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mempool-backend",
|
"name": "mempool-backend",
|
||||||
"version": "2.2.0-dev",
|
"version": "2.2.0",
|
||||||
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
||||||
"license": "GNU Affero General Public License v3.0",
|
"license": "GNU Affero General Public License v3.0",
|
||||||
"homepage": "https://mempool.space",
|
"homepage": "https://mempool.space",
|
||||||
@@ -37,13 +37,13 @@
|
|||||||
"locutus": "^2.0.12",
|
"locutus": "^2.0.12",
|
||||||
"mysql2": "2.2.5",
|
"mysql2": "2.2.5",
|
||||||
"node-worker-threads-pool": "^1.4.3",
|
"node-worker-threads-pool": "^1.4.3",
|
||||||
"ws": "^7.4.4"
|
"ws": "^7.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.0.1",
|
"@types/compression": "^1.0.1",
|
||||||
"@types/express": "^4.17.2",
|
"@types/express": "^4.17.2",
|
||||||
"@types/locutus": "^0.0.6",
|
"@types/locutus": "^0.0.6",
|
||||||
"@types/ws": "^6.0.4",
|
"@types/ws": "^7.4.4",
|
||||||
"tslint": "^6.1.0",
|
"tslint": "^6.1.0",
|
||||||
"typescript": "^4.1.5"
|
"typescript": "^4.1.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import { StaticPool } from 'node-worker-threads-pool';
|
|||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
|
|
||||||
class Bisq {
|
class Bisq {
|
||||||
private static BLOCKS_JSON_FILE_PATH = config.BISQ_BLOCKS.DATA_PATH + '/all/blocks.json';
|
private static BLOCKS_JSON_FILE_PATH = config.BISQ.DATA_PATH + '/json/all/blocks.json';
|
||||||
private latestBlockHeight = 0;
|
private latestBlockHeight = 0;
|
||||||
private blocks: BisqBlock[] = [];
|
private blocks: BisqBlock[] = [];
|
||||||
|
private allBlocks: BisqBlock[] = [];
|
||||||
private transactions: BisqTransaction[] = [];
|
private transactions: BisqTransaction[] = [];
|
||||||
private transactionIndex: { [txId: string]: BisqTransaction } = {};
|
private transactionIndex: { [txId: string]: BisqTransaction } = {};
|
||||||
private blockIndex: { [hash: string]: BisqBlock } = {};
|
private blockIndex: { [hash: string]: BisqBlock } = {};
|
||||||
@@ -98,7 +99,7 @@ class Bisq {
|
|||||||
this.topDirectoryWatcher.close();
|
this.topDirectoryWatcher.close();
|
||||||
}
|
}
|
||||||
let fsWait: NodeJS.Timeout | null = null;
|
let fsWait: NodeJS.Timeout | null = null;
|
||||||
this.topDirectoryWatcher = fs.watch(config.BISQ_BLOCKS.DATA_PATH, () => {
|
this.topDirectoryWatcher = fs.watch(config.BISQ.DATA_PATH + '/json', () => {
|
||||||
if (fsWait) {
|
if (fsWait) {
|
||||||
clearTimeout(fsWait);
|
clearTimeout(fsWait);
|
||||||
}
|
}
|
||||||
@@ -126,7 +127,7 @@ class Bisq {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let fsWait: NodeJS.Timeout | null = null;
|
let fsWait: NodeJS.Timeout | null = null;
|
||||||
this.subdirectoryWatcher = fs.watch(config.BISQ_BLOCKS.DATA_PATH + '/all', () => {
|
this.subdirectoryWatcher = fs.watch(config.BISQ.DATA_PATH + '/json/all', () => {
|
||||||
if (fsWait) {
|
if (fsWait) {
|
||||||
clearTimeout(fsWait);
|
clearTimeout(fsWait);
|
||||||
}
|
}
|
||||||
@@ -171,7 +172,7 @@ class Bisq {
|
|||||||
this.transactionIndex = {};
|
this.transactionIndex = {};
|
||||||
this.addressIndex = {};
|
this.addressIndex = {};
|
||||||
|
|
||||||
this.blocks.forEach((block) => {
|
this.allBlocks.forEach((block) => {
|
||||||
/* Build block index */
|
/* Build block index */
|
||||||
if (!this.blockIndex[block.hash]) {
|
if (!this.blockIndex[block.hash]) {
|
||||||
this.blockIndex[block.hash] = block;
|
this.blockIndex[block.hash] = block;
|
||||||
@@ -245,9 +246,10 @@ class Bisq {
|
|||||||
if (cacheData && cacheData.length !== 0) {
|
if (cacheData && cacheData.length !== 0) {
|
||||||
logger.debug('Processing Bisq data dump...');
|
logger.debug('Processing Bisq data dump...');
|
||||||
const data: BisqBlocks = await this.jsonParsePool.exec(cacheData);
|
const data: BisqBlocks = await this.jsonParsePool.exec(cacheData);
|
||||||
if (data.blocks && data.blocks.length !== this.blocks.length) {
|
if (data.blocks && data.blocks.length !== this.allBlocks.length) {
|
||||||
this.blocks = data.blocks.filter((block) => block.txs.length > 0);
|
this.allBlocks = data.blocks;
|
||||||
this.blocks.reverse();
|
this.allBlocks.reverse();
|
||||||
|
this.blocks = this.allBlocks.filter((block) => block.txs.length > 0);
|
||||||
this.latestBlockHeight = data.chainHeight;
|
this.latestBlockHeight = data.chainHeight;
|
||||||
const time = new Date().getTime() - start;
|
const time = new Date().getTime() - start;
|
||||||
logger.debug('Bisq dump processed in ' + time + ' ms (worker thread)');
|
logger.debug('Bisq dump processed in ' + time + ' ms (worker thread)');
|
||||||
|
|||||||
@@ -457,6 +457,30 @@ class BisqMarketsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVolumesByTime(time: number): MarketVolume[] {
|
||||||
|
const timestamp_from = new Date().getTime() / 1000 - time;
|
||||||
|
const timestamp_to = new Date().getTime() / 1000;
|
||||||
|
|
||||||
|
const trades = this.getTradesByCriteria(undefined, timestamp_to, timestamp_from,
|
||||||
|
undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
|
||||||
|
|
||||||
|
const markets: any = {};
|
||||||
|
|
||||||
|
for (const trade of trades) {
|
||||||
|
if (!markets[trade._market]) {
|
||||||
|
markets[trade._market] = {
|
||||||
|
'volume': 0,
|
||||||
|
'num_trades': 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
markets[trade._market]['volume'] += this.fiatCurrenciesIndexed[trade.currency] ? trade._tradeAmount : trade._tradeVolume;
|
||||||
|
markets[trade._market]['num_trades']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return markets;
|
||||||
|
}
|
||||||
|
|
||||||
private getTradesSummarized(trades: TradesData[], timestamp_from: number, interval?: string): SummarizedIntervals {
|
private getTradesSummarized(trades: TradesData[], timestamp_from: number, interval?: string): SummarizedIntervals {
|
||||||
const intervals: any = {};
|
const intervals: any = {};
|
||||||
const intervals_prices: any = {};
|
const intervals_prices: any = {};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import logger from '../../logger';
|
|||||||
|
|
||||||
class Bisq {
|
class Bisq {
|
||||||
private static FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE = 4000;
|
private static FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE = 4000;
|
||||||
private static MARKET_JSON_PATH = config.BISQ_MARKETS.DATA_PATH;
|
private static MARKET_JSON_PATH = config.BISQ.DATA_PATH;
|
||||||
private static MARKET_JSON_FILE_PATHS = {
|
private static MARKET_JSON_FILE_PATHS = {
|
||||||
activeCryptoCurrency: '/active_crypto_currency_list.json',
|
activeCryptoCurrency: '/active_crypto_currency_list.json',
|
||||||
activeFiatCurrency: '/active_fiat_currency_list.json',
|
activeFiatCurrency: '/active_fiat_currency_list.json',
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout);
|
esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout);
|
||||||
} else {
|
} else {
|
||||||
esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction);
|
esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction);
|
||||||
|
if (addPrevout) {
|
||||||
|
esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return esploraTransaction;
|
return esploraTransaction;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class FeeApi {
|
|||||||
const multiplier = (pBlock.blockVSize - 500000) / 500000;
|
const multiplier = (pBlock.blockVSize - 500000) / 500000;
|
||||||
return Math.max(Math.round(useFee * multiplier), this.defaultFee);
|
return Math.max(Math.round(useFee * multiplier), this.defaultFee);
|
||||||
}
|
}
|
||||||
return Math.round(useFee);
|
return Math.ceil(useFee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class FiatConversion {
|
|||||||
|
|
||||||
public startService() {
|
public startService() {
|
||||||
logger.info('Starting currency rates service');
|
logger.info('Starting currency rates service');
|
||||||
setInterval(this.updateCurrency.bind(this), 1000 * 60 * 60);
|
setInterval(this.updateCurrency.bind(this), 1000 * 60);
|
||||||
this.updateCurrency();
|
this.updateCurrency();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
this.wss.on('connection', (client: WebSocket) => {
|
this.wss.on('connection', (client: WebSocket) => {
|
||||||
client.on('error', logger.info);
|
client.on('error', logger.info);
|
||||||
client.on('message', (message: string) => {
|
client.on('message', async (message: string) => {
|
||||||
try {
|
try {
|
||||||
const parsedMessage: WebsocketResponse = JSON.parse(message);
|
const parsedMessage: WebsocketResponse = JSON.parse(message);
|
||||||
const response = {};
|
const response = {};
|
||||||
@@ -53,7 +53,16 @@ class WebsocketHandler {
|
|||||||
if (parsedMessage['watch-mempool']) {
|
if (parsedMessage['watch-mempool']) {
|
||||||
const tx = memPool.getMempool()[client['track-tx']];
|
const tx = memPool.getMempool()[client['track-tx']];
|
||||||
if (tx) {
|
if (tx) {
|
||||||
response['tx'] = tx;
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
|
try {
|
||||||
|
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||||
|
response['tx'] = fullTx;
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Error finding transaction in mempool: ' + e.message || e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response['tx'] = tx;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
client['track-mempool-tx'] = parsedMessage['track-tx'];
|
client['track-mempool-tx'] = parsedMessage['track-tx'];
|
||||||
}
|
}
|
||||||
@@ -96,6 +105,14 @@ class WebsocketHandler {
|
|||||||
client['track-donation'] = parsedMessage['track-donation'];
|
client['track-donation'] = parsedMessage['track-donation'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parsedMessage['track-bisq-market']) {
|
||||||
|
if (/^[a-z]{3}_[a-z]{3}$/.test(parsedMessage['track-bisq-market'])) {
|
||||||
|
client['track-bisq-market'] = parsedMessage['track-bisq-market'];
|
||||||
|
} else {
|
||||||
|
client['track-bisq-market'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
client.send(JSON.stringify(response));
|
client.send(JSON.stringify(response));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,11 +52,7 @@ interface IConfig {
|
|||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
TX_PER_SECOND_SAMPLE_PERIOD: number;
|
TX_PER_SECOND_SAMPLE_PERIOD: number;
|
||||||
};
|
};
|
||||||
BISQ_BLOCKS: {
|
BISQ: {
|
||||||
ENABLED: boolean;
|
|
||||||
DATA_PATH: string;
|
|
||||||
};
|
|
||||||
BISQ_MARKETS: {
|
|
||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
DATA_PATH: string;
|
DATA_PATH: string;
|
||||||
};
|
};
|
||||||
@@ -114,11 +110,7 @@ const defaults: IConfig = {
|
|||||||
'ENABLED': true,
|
'ENABLED': true,
|
||||||
'TX_PER_SECOND_SAMPLE_PERIOD': 150
|
'TX_PER_SECOND_SAMPLE_PERIOD': 150
|
||||||
},
|
},
|
||||||
'BISQ_BLOCKS': {
|
'BISQ': {
|
||||||
'ENABLED': false,
|
|
||||||
'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db/json'
|
|
||||||
},
|
|
||||||
'BISQ_MARKETS': {
|
|
||||||
'ENABLED': false,
|
'ENABLED': false,
|
||||||
'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db'
|
'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db'
|
||||||
},
|
},
|
||||||
@@ -133,8 +125,7 @@ class Config implements IConfig {
|
|||||||
DATABASE: IConfig['DATABASE'];
|
DATABASE: IConfig['DATABASE'];
|
||||||
SYSLOG: IConfig['SYSLOG'];
|
SYSLOG: IConfig['SYSLOG'];
|
||||||
STATISTICS: IConfig['STATISTICS'];
|
STATISTICS: IConfig['STATISTICS'];
|
||||||
BISQ_BLOCKS: IConfig['BISQ_BLOCKS'];
|
BISQ: IConfig['BISQ'];
|
||||||
BISQ_MARKETS: IConfig['BISQ_MARKETS'];
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const configs = this.merge(configFile, defaults);
|
const configs = this.merge(configFile, defaults);
|
||||||
@@ -146,8 +137,7 @@ class Config implements IConfig {
|
|||||||
this.DATABASE = configs.DATABASE;
|
this.DATABASE = configs.DATABASE;
|
||||||
this.SYSLOG = configs.SYSLOG;
|
this.SYSLOG = configs.SYSLOG;
|
||||||
this.STATISTICS = configs.STATISTICS;
|
this.STATISTICS = configs.STATISTICS;
|
||||||
this.BISQ_BLOCKS = configs.BISQ_BLOCKS;
|
this.BISQ = configs.BISQ;
|
||||||
this.BISQ_MARKETS = configs.BISQ_MARKETS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
merge = (...objects: object[]): IConfig => {
|
merge = (...objects: object[]): IConfig => {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class Server {
|
|||||||
await checkDbConnection();
|
await checkDbConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) {
|
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isMaster) {
|
||||||
statistics.startStatistics();
|
statistics.startStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,13 +90,10 @@ class Server {
|
|||||||
this.setUpHttpApiRoutes();
|
this.setUpHttpApiRoutes();
|
||||||
this.runMainUpdateLoop();
|
this.runMainUpdateLoop();
|
||||||
|
|
||||||
if (config.BISQ_BLOCKS.ENABLED) {
|
if (config.BISQ.ENABLED) {
|
||||||
bisq.startBisqService();
|
bisq.startBisqService();
|
||||||
bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitProperties('bsq-price', price));
|
bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitProperties('bsq-price', price));
|
||||||
blocks.setNewBlockCallback(bisq.handleNewBitcoinBlock.bind(bisq));
|
blocks.setNewBlockCallback(bisq.handleNewBitcoinBlock.bind(bisq));
|
||||||
}
|
|
||||||
|
|
||||||
if (config.BISQ_MARKETS.ENABLED) {
|
|
||||||
bisqMarkets.startBisqService();
|
bisqMarkets.startBisqService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +207,7 @@ class Server {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.BISQ_BLOCKS.ENABLED) {
|
if (config.BISQ.ENABLED) {
|
||||||
this.app
|
this.app
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/stats', routes.getBisqStats)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/stats', routes.getBisqStats)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/tx/:txId', routes.getBisqTransaction)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/tx/:txId', routes.getBisqTransaction)
|
||||||
@@ -219,11 +216,6 @@ class Server {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/:index/:length', routes.getBisqBlocks)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/:index/:length', routes.getBisqBlocks)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/address/:address', routes.getBisqAddress)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/address/:address', routes.getBisqAddress)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/txs/:index/:length', routes.getBisqTransactions)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/txs/:index/:length', routes.getBisqTransactions)
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.BISQ_MARKETS.ENABLED) {
|
|
||||||
this.app
|
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/currencies', routes.getBisqMarketCurrencies.bind(routes))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/currencies', routes.getBisqMarketCurrencies.bind(routes))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/depth', routes.getBisqMarketDepth.bind(routes))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/depth', routes.getBisqMarketDepth.bind(routes))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/hloc', routes.getBisqMarketHloc.bind(routes))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/hloc', routes.getBisqMarketHloc.bind(routes))
|
||||||
@@ -232,6 +224,7 @@ class Server {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/ticker', routes.getBisqMarketTicker.bind(routes))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/ticker', routes.getBisqMarketTicker.bind(routes))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/trades', routes.getBisqMarketTrades.bind(routes))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/trades', routes.getBisqMarketTrades.bind(routes))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes', routes.getBisqMarketVolumes.bind(routes))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes', routes.getBisqMarketVolumes.bind(routes))
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes/7d', routes.getBisqMarketVolumes7d.bind(routes))
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getNetwork(): string {
|
private getNetwork(): string {
|
||||||
if (config.BISQ_BLOCKS.ENABLED) {
|
if (config.BISQ.ENABLED) {
|
||||||
return 'bisq';
|
return 'bisq';
|
||||||
}
|
}
|
||||||
if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') {
|
if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') {
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ export interface WebsocketResponse {
|
|||||||
'track-tx': string;
|
'track-tx': string;
|
||||||
'track-address': string;
|
'track-address': string;
|
||||||
'watch-mempool': boolean;
|
'watch-mempool': boolean;
|
||||||
|
'track-bisq-market': string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VbytesPerSecond {
|
export interface VbytesPerSecond {
|
||||||
|
|||||||
@@ -426,6 +426,15 @@ class Routes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getBisqMarketVolumes7d(req: Request, res: Response) {
|
||||||
|
const result = bisqMarket.getVolumesByTime(604800);
|
||||||
|
if (result) {
|
||||||
|
res.json(result);
|
||||||
|
} else {
|
||||||
|
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketVolumes7d error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private parseRequestParameters(requestParams: object, params: RequiredSpec): { [name: string]: any; } {
|
private parseRequestParameters(requestParams: object, params: RequiredSpec): { [name: string]: any; } {
|
||||||
const final = {};
|
const final = {};
|
||||||
for (const i in params) {
|
for (const i in params) {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
FROM node:12-buster-slim AS builder
|
FROM node:12-buster-slim AS builder
|
||||||
|
|
||||||
|
ARG commitHash
|
||||||
|
ENV DOCKER_COMMIT_HASH=${commitHash}
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
|
|||||||
5
frontend/.gitignore
vendored
5
frontend/.gitignore
vendored
@@ -54,3 +54,8 @@ src/resources/pools.json
|
|||||||
# environment config
|
# environment config
|
||||||
mempool-frontend-config.json
|
mempool-frontend-config.json
|
||||||
generated-config.js
|
generated-config.js
|
||||||
|
|
||||||
|
# e2e results
|
||||||
|
cypress/videos
|
||||||
|
cypress/screenshots
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ https://www.transifex.com/mempool/mempool/dashboard/
|
|||||||
* Dutch @m__btc
|
* Dutch @m__btc
|
||||||
* Japanese @wiz @japananon
|
* Japanese @wiz @japananon
|
||||||
* Norwegian @T82771355
|
* Norwegian @T82771355
|
||||||
|
* Polish @maciejsoltysiak
|
||||||
* Portugese @jgcastro1985
|
* Portugese @jgcastro1985
|
||||||
* Slovenian @thepkbadger
|
* Slovenian @thepkbadger
|
||||||
* Finnish @bio_bitcoin
|
* Finnish @bio_bitcoin
|
||||||
@@ -30,3 +31,4 @@ https://www.transifex.com/mempool/mempool/dashboard/
|
|||||||
* Ukrainian @volbil
|
* Ukrainian @volbil
|
||||||
* Vietnamese @bitcoin_vietnam
|
* Vietnamese @bitcoin_vietnam
|
||||||
* Chinese @wdljt
|
* Chinese @wdljt
|
||||||
|
* Russian @TonyCrusoe @Bitconan
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
"i18n": {
|
"i18n": {
|
||||||
"sourceLocale": {
|
"sourceLocale": {
|
||||||
"code":"en-US",
|
"code": "en-US",
|
||||||
"baseHref":"/"
|
"baseHref": "/"
|
||||||
},
|
},
|
||||||
"locales": {
|
"locales": {
|
||||||
"ar": {
|
"ar": {
|
||||||
@@ -71,6 +71,10 @@
|
|||||||
"translation": "src/locale/messages.nb.xlf",
|
"translation": "src/locale/messages.nb.xlf",
|
||||||
"baseHref": "/nb/"
|
"baseHref": "/nb/"
|
||||||
},
|
},
|
||||||
|
"pl": {
|
||||||
|
"translation": "src/locale/messages.pl.xlf",
|
||||||
|
"baseHref": "/pl/"
|
||||||
|
},
|
||||||
"pt": {
|
"pt": {
|
||||||
"translation": "src/locale/messages.pt.xlf",
|
"translation": "src/locale/messages.pt.xlf",
|
||||||
"baseHref": "/pt/"
|
"baseHref": "/pt/"
|
||||||
@@ -106,6 +110,10 @@
|
|||||||
"zh": {
|
"zh": {
|
||||||
"translation": "src/locale/messages.zh.xlf",
|
"translation": "src/locale/messages.zh.xlf",
|
||||||
"baseHref": "/zh/"
|
"baseHref": "/zh/"
|
||||||
|
},
|
||||||
|
"ru": {
|
||||||
|
"translation": "src/locale/messages.ru.xlf",
|
||||||
|
"baseHref": "/ru/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -169,6 +177,22 @@
|
|||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "mempool:build:production"
|
"browserTarget": "mempool:build:production"
|
||||||
|
},
|
||||||
|
"local": {
|
||||||
|
"proxyConfig": "proxy.conf.json",
|
||||||
|
"verbose": true
|
||||||
|
},
|
||||||
|
"staging": {
|
||||||
|
"proxyConfig": "proxy.stg.conf.json",
|
||||||
|
"disableHostCheck": true,
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"verbose": true
|
||||||
|
},
|
||||||
|
"local-prod": {
|
||||||
|
"proxyConfig": "proxy.prod.conf.json",
|
||||||
|
"disableHostCheck": true,
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"verbose": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -201,8 +225,8 @@
|
|||||||
"tsConfig": [
|
"tsConfig": [
|
||||||
"tsconfig.app.json",
|
"tsconfig.app.json",
|
||||||
"tsconfig.spec.json",
|
"tsconfig.spec.json",
|
||||||
"e2e/tsconfig.json",
|
"tsconfig.server.json",
|
||||||
"tsconfig.server.json"
|
"cypress/tsconfig.json"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"**/node_modules/**"
|
"**/node_modules/**"
|
||||||
@@ -210,10 +234,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"e2e": {
|
"e2e": {
|
||||||
"builder": "@angular-devkit/build-angular:protractor",
|
"builder": "@cypress/schematic:cypress",
|
||||||
"options": {
|
"options": {
|
||||||
"protractorConfig": "e2e/protractor.conf.js",
|
"devServerTarget": "mempool:serve:local-prod",
|
||||||
"devServerTarget": "mempool:serve"
|
"watch": true,
|
||||||
|
"headless": false
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
@@ -268,8 +293,27 @@
|
|||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {}
|
"production": {}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cypress-run": {
|
||||||
|
"builder": "@cypress/schematic:cypress",
|
||||||
|
"options": {
|
||||||
|
"devServerTarget": "mempool:serve"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"devServerTarget": "mempool:serve:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cypress-open": {
|
||||||
|
"builder": "@cypress/schematic:cypress",
|
||||||
|
"options": {
|
||||||
|
"watch": true,
|
||||||
|
"headless": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
"defaultProject": "mempool"
|
"defaultProject": "mempool"
|
||||||
}
|
}
|
||||||
|
|||||||
9
frontend/cypress.json
Normal file
9
frontend/cypress.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"integrationFolder": "cypress/integration",
|
||||||
|
"supportFile": "cypress/support/index.ts",
|
||||||
|
"videosFolder": "cypress/videos",
|
||||||
|
"screenshotsFolder": "cypress/screenshots",
|
||||||
|
"pluginsFile": "cypress/plugins/index.js",
|
||||||
|
"fixturesFolder": "cypress/fixtures",
|
||||||
|
"baseUrl": "http://localhost:4200"
|
||||||
|
}
|
||||||
119
frontend/cypress/fixtures/assets.json
Normal file
119
frontend/cypress/fixtures/assets.json
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"f59c5f3e8141f322276daa63ed5f307085808aea6d4ef9ba61e28154533fdec7": {
|
||||||
|
"asset_id": "f59c5f3e8141f322276daa63ed5f307085808aea6d4ef9ba61e28154533fdec7",
|
||||||
|
"contract": {
|
||||||
|
"entity": {
|
||||||
|
"domain": "listedreserve.com"
|
||||||
|
},
|
||||||
|
"issuer_pubkey": "031cc579d142a03b33cdd745922112821c16e5e8b74e3bd57f16f7fda872b6f1d0",
|
||||||
|
"name": "Liquid AUD",
|
||||||
|
"precision": 2,
|
||||||
|
"ticker": "AUDL",
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"issuance_txin": {
|
||||||
|
"txid": "e5c5144ba3dc48259ae29023fe9f7775dec1fc049f456dd3d1f7178e31901fb5",
|
||||||
|
"vin": 0
|
||||||
|
},
|
||||||
|
"issuance_prevout": {
|
||||||
|
"txid": "ed48be2e035ffa425d2c6faaa82b6a7b648aed1246b6ac76c72e0408db8cf057",
|
||||||
|
"vout": 1
|
||||||
|
},
|
||||||
|
"name": "Liquid AUD",
|
||||||
|
"ticker": "AUDL",
|
||||||
|
"precision": 2,
|
||||||
|
"entity": {
|
||||||
|
"domain": "listedreserve.com"
|
||||||
|
},
|
||||||
|
"version": 0,
|
||||||
|
"issuer_pubkey": "031cc579d142a03b33cdd745922112821c16e5e8b74e3bd57f16f7fda872b6f1d0"
|
||||||
|
},
|
||||||
|
"0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a": {
|
||||||
|
"asset_id": "0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a",
|
||||||
|
"contract": {
|
||||||
|
"entity": {
|
||||||
|
"domain": "lcad.bullbitcoin.com"
|
||||||
|
},
|
||||||
|
"issuer_pubkey": "027fa34026195b05f3aa217335416811dca4f5b579d00271a1bb6304c0152458a8",
|
||||||
|
"name": "Liquid CAD",
|
||||||
|
"precision": 8,
|
||||||
|
"ticker": "LCAD",
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"issuance_txin": {
|
||||||
|
"txid": "238badf029cadcf546d90ce23c7eafc2fa2082585c9bd62dc26f1aa11c7bd850",
|
||||||
|
"vin": 0
|
||||||
|
},
|
||||||
|
"issuance_prevout": {
|
||||||
|
"txid": "a87f13917c08c7ccd8eddb1830c5c9a2bcd59c7d167e9d528659ba40808a6b76",
|
||||||
|
"vout": 0
|
||||||
|
},
|
||||||
|
"name": "Liquid CAD",
|
||||||
|
"ticker": "LCAD",
|
||||||
|
"precision": 8,
|
||||||
|
"entity": {
|
||||||
|
"domain": "lcad.bullbitcoin.com"
|
||||||
|
},
|
||||||
|
"version": 0,
|
||||||
|
"issuer_pubkey": "027fa34026195b05f3aa217335416811dca4f5b579d00271a1bb6304c0152458a8"
|
||||||
|
},
|
||||||
|
"3438ecb49fc45c08e687de4749ed628c511e326460ea4336794e1cf02741329e": {
|
||||||
|
"asset_id": "3438ecb49fc45c08e687de4749ed628c511e326460ea4336794e1cf02741329e",
|
||||||
|
"contract": {
|
||||||
|
"entity": {
|
||||||
|
"domain": "settlenet.io"
|
||||||
|
},
|
||||||
|
"issuer_pubkey": "037b09d542bf7cea6a19fa624b4441790c1a6e44823597bf190e981a846a196541",
|
||||||
|
"name": "SETTLENET JPY Stablecoin by Crypto Garage",
|
||||||
|
"precision": 0,
|
||||||
|
"ticker": "JPYS",
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"issuance_txin": {
|
||||||
|
"txid": "e33ad5ce8879297d8bfa7daa193920b94abd3fb12f4e8dade9543dbb292387cb",
|
||||||
|
"vin": 0
|
||||||
|
},
|
||||||
|
"issuance_prevout": {
|
||||||
|
"txid": "328c4fadd817ea75e634e3648eb4be0bf7e669539b8da921c0f77af3bc148894",
|
||||||
|
"vout": 1
|
||||||
|
},
|
||||||
|
"name": "SETTLENET JPY Stablecoin by Crypto Garage",
|
||||||
|
"ticker": "JPYS",
|
||||||
|
"precision": 0,
|
||||||
|
"entity": {
|
||||||
|
"domain": "settlenet.io"
|
||||||
|
},
|
||||||
|
"version": 0,
|
||||||
|
"issuer_pubkey": "037b09d542bf7cea6a19fa624b4441790c1a6e44823597bf190e981a846a196541"
|
||||||
|
},
|
||||||
|
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2": {
|
||||||
|
"asset_id": "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2",
|
||||||
|
"contract": {
|
||||||
|
"entity": {
|
||||||
|
"domain": "tether.to"
|
||||||
|
},
|
||||||
|
"issuer_pubkey": "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904",
|
||||||
|
"name": "Tether USD",
|
||||||
|
"precision": 8,
|
||||||
|
"ticker": "USDt",
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"issuance_txin": {
|
||||||
|
"txid": "abb4080d91849e933ee2ed65da6b436f7c385cf363fb4aa08399f1e27c58ff3d",
|
||||||
|
"vin": 0
|
||||||
|
},
|
||||||
|
"issuance_prevout": {
|
||||||
|
"txid": "9596d259270ef5bac0020435e6d859aea633409483ba64e232b8ba04ce288668",
|
||||||
|
"vout": 0
|
||||||
|
},
|
||||||
|
"name": "Tether USD",
|
||||||
|
"ticker": "USDt",
|
||||||
|
"precision": 8,
|
||||||
|
"entity": {
|
||||||
|
"domain": "tether.to"
|
||||||
|
},
|
||||||
|
"version": 0,
|
||||||
|
"issuer_pubkey": "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
33
frontend/cypress/fixtures/assets.minimal.json
Normal file
33
frontend/cypress/fixtures/assets.minimal.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"f59c5f3e8141f322276daa63ed5f307085808aea6d4ef9ba61e28154533fdec7": [
|
||||||
|
"listedreserve.com",
|
||||||
|
"AUDL",
|
||||||
|
"Liquid AUD",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a": [
|
||||||
|
"lcad.bullbitcoin.com",
|
||||||
|
"LCAD",
|
||||||
|
"Liquid CAD",
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d": [
|
||||||
|
null,
|
||||||
|
"L-BTC",
|
||||||
|
"Liquid Bitcoin",
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2": [
|
||||||
|
"tether.to",
|
||||||
|
"USDt",
|
||||||
|
"Tether USD",
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"3438ecb49fc45c08e687de4749ed628c511e326460ea4336794e1cf02741329e": [
|
||||||
|
"settlenet.io",
|
||||||
|
"JPYS",
|
||||||
|
"SETTLENET JPY Stablecoin by Crypto Garage",
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
1178
frontend/cypress/fixtures/pools.json
Normal file
1178
frontend/cypress/fixtures/pools.json
Normal file
File diff suppressed because it is too large
Load Diff
51
frontend/cypress/integration/bisq/bisq.spec.ts
Normal file
51
frontend/cypress/integration/bisq/bisq.spec.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
describe('Bisq', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
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');
|
||||||
|
cy.intercept('/bisq/api/markets/markets').as('markets');
|
||||||
|
cy.intercept('/bisq/api/markets/volumes/7d').as('7d');
|
||||||
|
cy.intercept('/bisq/api/markets/trades?market=all').as('trades');
|
||||||
|
cy.intercept('/bisq/api/txs/*/*').as('txs');
|
||||||
|
cy.intercept('/bisq/api/blocks/*/*').as('blocks');
|
||||||
|
cy.intercept('/bisq/api/stats').as('stats');
|
||||||
|
});
|
||||||
|
it('loads the dashboard', () => {
|
||||||
|
cy.visit('/bisq');
|
||||||
|
|
||||||
|
cy.wait('@socket');
|
||||||
|
cy.wait('@hloc');
|
||||||
|
cy.wait('@ticker');
|
||||||
|
cy.wait('@markets');
|
||||||
|
cy.wait('@7d');
|
||||||
|
cy.wait('@trades');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the transactions screen', () => {
|
||||||
|
cy.visit('/bisq');
|
||||||
|
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||||
|
cy.wait('@txs');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('loads the blocks screen', () => {
|
||||||
|
cy.visit('/bisq');
|
||||||
|
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||||
|
cy.wait('@blocks');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('loads the stats screen', () => {
|
||||||
|
cy.visit('/bisq');
|
||||||
|
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||||
|
cy.wait('@stats');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the api screen', () => {
|
||||||
|
cy.visit('/bisq');
|
||||||
|
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
81
frontend/cypress/integration/liquid/liquid.spec.ts
Normal file
81
frontend/cypress/integration/liquid/liquid.spec.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
describe('Liquid', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// TODO: Fix ng serve to deliver these files
|
||||||
|
cy.fixture('assets.minimal').then((json) => {
|
||||||
|
cy.intercept('/resources/assets.minimal.json', json);
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.fixture('assets').then((json) => {
|
||||||
|
cy.intercept('/resources/assets.json', json);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the dashboard', () => {
|
||||||
|
cy.visit('/liquid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the blocks page', () => {
|
||||||
|
cy.visit('/liquid/blocks');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads a specific block page', () => {
|
||||||
|
cy.visit('/liquid/blocks');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the graphs page', () => {
|
||||||
|
cy.visit('/liquid/graphs');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the tv page - desktop', () => {
|
||||||
|
cy.visit('/liquid');
|
||||||
|
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the graphs page - mobile', () => {
|
||||||
|
cy.visit('/liquid');
|
||||||
|
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||||
|
cy.viewport('iphone-6');
|
||||||
|
cy.wait(1000);
|
||||||
|
// TODO: Should we really support TV Mode in Mobile for Bisq?
|
||||||
|
// cy.get('.tv-only').should('be.visible')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('assets', () => {
|
||||||
|
it('shows the assets screen', () => {
|
||||||
|
cy.visit('/liquid');
|
||||||
|
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||||
|
cy.get('table tr').should('have.length', 5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows searching assets', () => {
|
||||||
|
cy.visit('/liquid');
|
||||||
|
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||||
|
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
|
||||||
|
cy.get('table tr').should('have.length', 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a specific asset ID', () => {
|
||||||
|
cy.visit('/liquid');
|
||||||
|
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||||
|
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
|
||||||
|
cy.get('table tr td:nth-of-type(4) a').click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a specific asset issuance TX', () => {
|
||||||
|
cy.visit('/liquid');
|
||||||
|
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||||
|
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
|
||||||
|
cy.get('table tr td:nth-of-type(5) a').click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
98
frontend/cypress/integration/mainnet/mainnet.spec.ts
Normal file
98
frontend/cypress/integration/mainnet/mainnet.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
describe('Mainnet', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.intercept('/api/block-height/*').as('block-height');
|
||||||
|
cy.intercept('/api/block/*').as('block');
|
||||||
|
cy.intercept('/api/block/*/txs/0').as('block-txs');
|
||||||
|
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
|
||||||
|
|
||||||
|
// TODO: Fix ng serve to deliver this file
|
||||||
|
cy.fixture('pools').then((json) => {
|
||||||
|
cy.intercept('/resources/pools.json', json);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the dashboard', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the blocks screen', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
cy.get('li:nth-of-type(2) > a').click().then(() => {
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the graphs screen', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
cy.get('li:nth-of-type(3) > a').click().then(() => {
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tv mode', () => {
|
||||||
|
it('loads the tv screen - desktop', () => {
|
||||||
|
cy.viewport('macbook-16');
|
||||||
|
cy.visit('/');
|
||||||
|
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||||
|
cy.viewport('macbook-16');
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.get('.blockchain-wrapper').should('be.visible');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the tv screen - mobile', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
cy.get('li:nth-of-type(4) > a').click().then(() => {
|
||||||
|
cy.viewport('iphone-6');
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.get('.blockchain-wrapper').should('not.be.visible');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('loads the api screen', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
cy.get('li:nth-of-type(5) > a').click().then(() => {
|
||||||
|
cy.wait(1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('blocks', () => {
|
||||||
|
it('shows empty blocks properly', () => {
|
||||||
|
cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775');
|
||||||
|
cy.get('h2').invoke('text').should('equal', '1 transaction');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('expands and collapses the block details', () => {
|
||||||
|
cy.visit('/block/0');
|
||||||
|
cy.wait('@tx-outspends');
|
||||||
|
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||||
|
cy.get('#details').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('.btn.btn-outline-info').click().then(() => {
|
||||||
|
cy.get('#details').should('not.be.visible');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows blocks with no pagination', () => {
|
||||||
|
cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4');
|
||||||
|
cy.get('h2').invoke('text').should('equal', '19 transactions');
|
||||||
|
cy.get('ul.pagination').first().children().should('have.length', 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports pagination on the block screen', () => {
|
||||||
|
// 41 txs
|
||||||
|
cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8');
|
||||||
|
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
|
||||||
|
cy.get('.active + li').first().click().then(() => {
|
||||||
|
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
|
||||||
|
expect(text1).not.to.eq(text2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
10
frontend/cypress/integration/signet/signet.spec.ts
Normal file
10
frontend/cypress/integration/signet/signet.spec.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
describe('Signet', () => {
|
||||||
|
it('loads the dashboard', () => {
|
||||||
|
cy.visit('/signet');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('loads all the pages properly', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
9
frontend/cypress/integration/testnet/testnet.spec.ts
Normal file
9
frontend/cypress/integration/testnet/testnet.spec.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
describe('Testnet', () => {
|
||||||
|
it('loads the dashboard', () => {
|
||||||
|
cy.visit('/testnet');
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('loads all the pages properly', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
1
frontend/cypress/plugins/index.js
Normal file
1
frontend/cypress/plugins/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = (on, config) => {}
|
||||||
45
frontend/cypress/support/commands.ts
Normal file
45
frontend/cypress/support/commands.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example namespace declaration will help
|
||||||
|
// with Intellisense and code completion in your
|
||||||
|
// IDE or Text Editor.
|
||||||
|
// ***********************************************
|
||||||
|
// declare namespace Cypress {
|
||||||
|
// interface Chainable<Subject = any> {
|
||||||
|
// customCommand(param: any): typeof customCommand;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function customCommand(param: any): void {
|
||||||
|
// console.warn(param);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// NOTE: You can use it like so:
|
||||||
|
// Cypress.Commands.add('customCommand', customCommand);
|
||||||
|
//
|
||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||||
|
|
||||||
|
import 'cypress-wait-until';
|
||||||
17
frontend/cypress/support/index.ts
Normal file
17
frontend/cypress/support/index.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// When a command from ./commands is ready to use, import with `import './commands'` syntax
|
||||||
|
import './commands';
|
||||||
8
frontend/cypress/tsconfig.json
Normal file
8
frontend/cypress/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": ["**/*.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": false,
|
||||||
|
"types": ["cypress"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
// Protractor configuration file, see link for more information
|
|
||||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
|
||||||
|
|
||||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type { import("protractor").Config }
|
|
||||||
*/
|
|
||||||
exports.config = {
|
|
||||||
allScriptsTimeout: 11000,
|
|
||||||
specs: [
|
|
||||||
'./src/**/*.e2e-spec.ts'
|
|
||||||
],
|
|
||||||
capabilities: {
|
|
||||||
'browserName': 'chrome'
|
|
||||||
},
|
|
||||||
directConnect: true,
|
|
||||||
baseUrl: 'http://localhost:4200/',
|
|
||||||
framework: 'jasmine',
|
|
||||||
jasmineNodeOpts: {
|
|
||||||
showColors: true,
|
|
||||||
defaultTimeoutInterval: 30000,
|
|
||||||
print: function() {}
|
|
||||||
},
|
|
||||||
onPrepare() {
|
|
||||||
require('ts-node').register({
|
|
||||||
project: require('path').join(__dirname, './tsconfig.json')
|
|
||||||
});
|
|
||||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { AppPage } from './app.po';
|
|
||||||
import { browser, logging } from 'protractor';
|
|
||||||
|
|
||||||
describe('workspace-project App', () => {
|
|
||||||
let page: AppPage;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
page = new AppPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display welcome message', () => {
|
|
||||||
page.navigateTo();
|
|
||||||
expect(page.getTitleText()).toEqual('Welcome to mempool!');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
// Assert that there are no errors emitted from the browser
|
|
||||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
|
||||||
expect(logs).not.toContain(jasmine.objectContaining({
|
|
||||||
level: logging.Level.SEVERE,
|
|
||||||
} as logging.Entry));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { browser, by, element } from 'protractor';
|
|
||||||
|
|
||||||
export class AppPage {
|
|
||||||
navigateTo() {
|
|
||||||
return browser.get(browser.baseUrl) as Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTitleText() {
|
|
||||||
return element(by.css('app-root h1')).getText() as Promise<string>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../out-tsc/e2e",
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es2018",
|
|
||||||
"types": [
|
|
||||||
"jasmine",
|
|
||||||
"jasminewd2",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||||
const GENERATED_CONFIG_FILE_NAME = 'generated-config.js';
|
const GENERATED_CONFIG_FILE_NAME = 'generated-config.js';
|
||||||
@@ -11,15 +12,19 @@ let packetJsonVersion = '';
|
|||||||
try {
|
try {
|
||||||
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
|
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
|
||||||
configContent = JSON.parse(rawConfig);
|
configContent = JSON.parse(rawConfig);
|
||||||
|
console.log(`${CONFIG_FILE_NAME} file found, using provided config`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code !== 'ENOENT') {
|
if (e.code !== 'ENOENT') {
|
||||||
throw new Error(e);
|
throw new Error(e);
|
||||||
|
} else {
|
||||||
|
console.log(`${CONFIG_FILE_NAME} file not found, using default config`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const packageJson = fs.readFileSync('package.json');
|
const packageJson = fs.readFileSync('package.json');
|
||||||
packetJsonVersion = JSON.parse(packageJson).version;
|
packetJsonVersion = JSON.parse(packageJson).version;
|
||||||
|
console.log(`mempool version ${packetJsonVersion}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(e);
|
throw new Error(e);
|
||||||
}
|
}
|
||||||
@@ -31,23 +36,64 @@ for (setting in configContent) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (process.env.DOCKER_COMMIT_HASH) {
|
||||||
gitCommitHash = fs.readFileSync('../.git/refs/heads/master').toString().trim();
|
gitCommitHash = process.env.DOCKER_COMMIT_HASH;
|
||||||
} catch (e) {
|
} else {
|
||||||
console.log('Could not load git commit info: ' + e.message || e);
|
try {
|
||||||
|
const gitRevParse = spawnSync('git', ['rev-parse', '--short', 'HEAD']);
|
||||||
|
|
||||||
|
if (!gitRevParse.error) {
|
||||||
|
gitCommitHash = gitRevParse.stdout.toString('utf-8').replace(/[\n\r\s]+$/, '');
|
||||||
|
console.log(`mempool revision ${gitCommitHash}`);
|
||||||
|
} else if (gitRevParse.error.code === 'ENOENT') {
|
||||||
|
console.log('git not found, cannot parse git hash');
|
||||||
|
gitCommitHash = '?';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Could not load git commit info: ' + e.message);
|
||||||
|
gitCommitHash = '?';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = `(function (window) {
|
const newConfig = `(function (window) {
|
||||||
window.__env = window.__env || {};${settings.reduce((str, obj) => `${str}
|
window.__env = window.__env || {};${settings.reduce((str, obj) => `${str}
|
||||||
window.__env.${obj.key} = ${ typeof obj.value === 'string' ? `'${obj.value}'` : obj.value };`, '')}
|
window.__env.${obj.key} = ${ typeof obj.value === 'string' ? `'${obj.value}'` : obj.value };`, '')}
|
||||||
window.__env.GIT_COMMIT_HASH = '${gitCommitHash}';
|
window.__env.GIT_COMMIT_HASH = '${gitCommitHash}';
|
||||||
window.__env.PACKAGE_JSON_VERSION = '${packetJsonVersion}';
|
window.__env.PACKAGE_JSON_VERSION = '${packetJsonVersion}';
|
||||||
}(global || this));`;
|
}(global || this));`;
|
||||||
|
|
||||||
try {
|
function readConfig(path) {
|
||||||
fs.writeFileSync(GENERATED_CONFIG_FILE_NAME, code, 'utf8');
|
try {
|
||||||
} catch (e) {
|
const currentConfig = fs.readFileSync(path).toString().trim();
|
||||||
throw new Error(e);
|
return currentConfig;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Config file generated');
|
function writeConfig(path, config) {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(path, config, 'utf8');
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME);
|
||||||
|
|
||||||
|
if (currentConfig && currentConfig === newConfig) {
|
||||||
|
console.log(`No configuration updates, skipping ${GENERATED_CONFIG_FILE_NAME} file update`);
|
||||||
|
return;
|
||||||
|
} else if (!currentConfig) {
|
||||||
|
console.log(`${GENERATED_CONFIG_FILE_NAME} file not found, creating new config file`);
|
||||||
|
console.log('CONFIG: ', newConfig);
|
||||||
|
writeConfig(GENERATED_CONFIG_FILE_NAME, newConfig);
|
||||||
|
console.log(`${GENERATED_CONFIG_FILE_NAME} file saved`);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log(`Configuration changes detected, updating ${GENERATED_CONFIG_FILE_NAME} file`);
|
||||||
|
console.log('OLD CONFIG: ', currentConfig);
|
||||||
|
console.log('NEW CONFIG: ', newConfig);
|
||||||
|
writeConfig(GENERATED_CONFIG_FILE_NAME, newConfig);
|
||||||
|
console.log(`${GENERATED_CONFIG_FILE_NAME} file updated`);
|
||||||
|
};
|
||||||
|
|||||||
9479
frontend/package-lock.json
generated
9479
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mempool-frontend",
|
"name": "mempool-frontend",
|
||||||
"version": "2.2.0-dev",
|
"version": "2.2.0",
|
||||||
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
||||||
"license": "GNU Affero General Public License v3.0",
|
"license": "GNU Affero General Public License v3.0",
|
||||||
"homepage": "https://mempool.space",
|
"homepage": "https://mempool.space",
|
||||||
@@ -24,8 +24,12 @@
|
|||||||
"tsc": "./node_modules/typescript/bin/tsc",
|
"tsc": "./node_modules/typescript/bin/tsc",
|
||||||
"i18n-extract-from-source": "./node_modules/@angular/cli/bin/ng xi18n --ivy --out-file ./src/locale/messages.xlf",
|
"i18n-extract-from-source": "./node_modules/@angular/cli/bin/ng xi18n --ivy --out-file ./src/locale/messages.xlf",
|
||||||
"i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1 --force",
|
"i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1 --force",
|
||||||
"serve": "ng serve --proxy-config proxy.conf.json",
|
"serve": "npm run generate-config && ng serve -c local",
|
||||||
"start": "npm run generate-config && npm run sync-assets-dev && ng serve --proxy-config proxy.conf.json",
|
"serve:stg": "npm run generate-config && ng serve -c staging",
|
||||||
|
"serve:local-prod": "npm run generate-config && ng serve -c local-prod",
|
||||||
|
"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 --prod --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": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
|
||||||
"sync-assets-dev": "node sync-assets.js dev",
|
"sync-assets-dev": "node sync-assets.js dev",
|
||||||
@@ -37,7 +41,9 @@
|
|||||||
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
|
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
|
||||||
"serve:ssr": "node server.run.js",
|
"serve:ssr": "node server.run.js",
|
||||||
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",
|
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",
|
||||||
"prerender": "ng run mempool:prerender"
|
"prerender": "ng run mempool:prerender",
|
||||||
|
"cypress:open": "concurrently \"ng serve -c local-prod\" \"cypress open\" --kill-others",
|
||||||
|
"cypress:run": "concurrently \"ng serve -c local-prod\" \"cypress run\" --kill-others"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "~11.2.8",
|
"@angular/animations": "~11.2.8",
|
||||||
@@ -55,7 +61,7 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||||
"@mempool/chartist": "^0.11.4",
|
"@mempool/chartist": "^0.11.4",
|
||||||
"@mempool/mempool.js": "^2.2.0",
|
"@mempool/mempool.js": "^2.2.2",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
||||||
"@nguniversal/express-engine": "11.2.1",
|
"@nguniversal/express-engine": "11.2.1",
|
||||||
"@types/qrcode": "^1.3.4",
|
"@types/qrcode": "^1.3.4",
|
||||||
@@ -64,6 +70,7 @@
|
|||||||
"clipboard": "^2.0.4",
|
"clipboard": "^2.0.4",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"lightweight-charts": "^3.3.0",
|
||||||
"ngx-bootrap-multiselect": "^2.0.0",
|
"ngx-bootrap-multiselect": "^2.0.0",
|
||||||
"ngx-infinite-scroll": "^10.0.1",
|
"ngx-infinite-scroll": "^10.0.1",
|
||||||
"qrcode": "^1.4.4",
|
"qrcode": "^1.4.4",
|
||||||
@@ -78,12 +85,15 @@
|
|||||||
"@angular/cli": "~11.2.7",
|
"@angular/cli": "~11.2.7",
|
||||||
"@angular/compiler-cli": "~11.2.8",
|
"@angular/compiler-cli": "~11.2.8",
|
||||||
"@angular/language-service": "~11.2.8",
|
"@angular/language-service": "~11.2.8",
|
||||||
|
"@cypress/schematic": "^1.3.0",
|
||||||
"@nguniversal/builders": "^11.2.1",
|
"@nguniversal/builders": "^11.2.1",
|
||||||
"@types/express": "^4.17.0",
|
"@types/express": "^4.17.0",
|
||||||
"@types/jasmine": "~3.6.0",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
"codelyzer": "^6.0.1",
|
"codelyzer": "^6.0.1",
|
||||||
|
"concurrently": "^6.2.0",
|
||||||
|
"cypress-wait-until": "^1.7.1",
|
||||||
"http-proxy-middleware": "^1.0.5",
|
"http-proxy-middleware": "^1.0.5",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~5.0.0",
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
@@ -92,9 +102,11 @@
|
|||||||
"karma-coverage": "~2.0.3",
|
"karma-coverage": "~2.0.3",
|
||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"protractor": "~7.0.0",
|
|
||||||
"ts-node": "~8.3.0",
|
"ts-node": "~8.3.0",
|
||||||
"tslint": "~6.1.0",
|
"tslint": "~6.1.0",
|
||||||
"typescript": "~4.1.5"
|
"typescript": "~4.1.5"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"cypress": "^7.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,5 +88,20 @@
|
|||||||
"pathRewrite": {
|
"pathRewrite": {
|
||||||
"^/bisq/api": "/api/v1/ws"
|
"^/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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
102
frontend/proxy.prod.conf.json
Normal file
102
frontend/proxy.prod.conf.json
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"/api/v1/ws": {
|
||||||
|
"target": "https://mempool.space",
|
||||||
|
"secure": false,
|
||||||
|
"ws": true
|
||||||
|
},
|
||||||
|
"/api/*": {
|
||||||
|
"target": "https://mempool.space",
|
||||||
|
"secure": false,
|
||||||
|
"changeOrigin": true,
|
||||||
|
"logLevel": "debug",
|
||||||
|
"pathRewrite": {
|
||||||
|
"^/api": "https://mempool.space/api"
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
100
frontend/proxy.stg.conf.json
Normal file
100
frontend/proxy.stg.conf.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"/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/v1/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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,10 @@ import { DashboardComponent } from './dashboard/dashboard.component';
|
|||||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
|
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
|
||||||
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
|
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
|
||||||
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
||||||
|
import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component';
|
||||||
|
import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
let routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: MasterPageComponent,
|
component: MasterPageComponent,
|
||||||
@@ -69,6 +71,10 @@ const routes: Routes = [
|
|||||||
children: [],
|
children: [],
|
||||||
component: AddressComponent
|
component: AddressComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'sponsor',
|
||||||
|
component: SponsorComponent,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -283,9 +289,23 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const browserWindow = window || {};
|
||||||
|
// @ts-ignore
|
||||||
|
const browserWindowEnv = browserWindow.__env || {};
|
||||||
|
|
||||||
|
if (browserWindowEnv && browserWindowEnv.OFFICIAL_BISQ_MARKETS) {
|
||||||
|
routes = [{
|
||||||
|
path: '',
|
||||||
|
component: BisqMasterPageComponent,
|
||||||
|
loadChildren: () => import('./bisq/bisq.module').then(m => m.BisqModule)
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes, {
|
imports: [RouterModule.forRoot(routes, {
|
||||||
initialNavigation: 'enabled'
|
initialNavigation: 'enabled',
|
||||||
|
scrollPositionRestoration: 'enabled',
|
||||||
|
anchorScrolling: 'enabled'
|
||||||
})],
|
})],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -71,11 +71,11 @@ export const languages: Language[] = [
|
|||||||
{ code: 'ja', name: '日本語' }, // Japanese
|
{ code: 'ja', name: '日本語' }, // Japanese
|
||||||
{ code: 'nb', name: 'Norsk' }, // Norwegian Bokmål
|
{ code: 'nb', name: 'Norsk' }, // Norwegian Bokmål
|
||||||
// { code: 'nn', name: 'Norsk Nynorsk' }, // Norwegian Nynorsk
|
// { code: 'nn', name: 'Norsk Nynorsk' }, // Norwegian Nynorsk
|
||||||
// { code: 'pl', name: 'Polski' }, // Polish
|
{ code: 'pl', name: 'Polski' }, // Polish
|
||||||
{ code: 'pt', name: 'Português' }, // Portuguese
|
{ code: 'pt', name: 'Português' }, // Portuguese
|
||||||
// { code: 'pt-BR', name: 'Português (Brazil)' }, // Portuguese (Brazil)
|
// { 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: 'ru', name: 'Русский' }, // Russian
|
||||||
// { code: 'sk', name: 'Slovenčina' }, // Slovak
|
// { code: 'sk', name: 'Slovenčina' }, // Slovak
|
||||||
{ code: 'sl', name: 'Slovenščina' }, // Slovenian
|
{ code: 'sl', name: 'Slovenščina' }, // Slovenian
|
||||||
// { code: 'sr', name: 'Српски / srpski' }, // Serbian
|
// { code: 'sr', name: 'Српски / srpski' }, // Serbian
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { WebsocketService } from './services/websocket.service';
|
|||||||
import { AddressLabelsComponent } from './components/address-labels/address-labels.component';
|
import { AddressLabelsComponent } from './components/address-labels/address-labels.component';
|
||||||
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.component';
|
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.component';
|
||||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||||
|
import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component';
|
||||||
import { AboutComponent } from './components/about/about.component';
|
import { AboutComponent } from './components/about/about.component';
|
||||||
import { TelevisionComponent } from './components/television/television.component';
|
import { TelevisionComponent } from './components/television/television.component';
|
||||||
import { StatisticsComponent } from './components/statistics/statistics.component';
|
import { StatisticsComponent } from './components/statistics/statistics.component';
|
||||||
@@ -44,17 +45,20 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component';
|
|||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||||
import { faAngleDown, faAngleUp, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
|
import { faAngleDown, faAngleUp, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
|
||||||
faLink, faList, faSearch, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faAngleDoubleUp } from '@fortawesome/free-solid-svg-icons';
|
faLink, faList, faSearch, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
|
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
|
||||||
|
import { CodeTemplateComponent } from './components/api-docs/code-template.component';
|
||||||
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
||||||
import { StorageService } from './services/storage.service';
|
import { StorageService } from './services/storage.service';
|
||||||
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
||||||
|
import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
AboutComponent,
|
AboutComponent,
|
||||||
MasterPageComponent,
|
MasterPageComponent,
|
||||||
|
BisqMasterPageComponent,
|
||||||
TelevisionComponent,
|
TelevisionComponent,
|
||||||
BlockchainComponent,
|
BlockchainComponent,
|
||||||
StartComponent,
|
StartComponent,
|
||||||
@@ -82,7 +86,9 @@ import { HttpCacheInterceptor } from './services/http-cache.interceptor';
|
|||||||
FeesBoxComponent,
|
FeesBoxComponent,
|
||||||
DashboardComponent,
|
DashboardComponent,
|
||||||
ApiDocsComponent,
|
ApiDocsComponent,
|
||||||
|
CodeTemplateComponent,
|
||||||
TermsOfServiceComponent,
|
TermsOfServiceComponent,
|
||||||
|
SponsorComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||||
@@ -127,5 +133,8 @@ export class AppModule {
|
|||||||
library.addIcons(faExchangeAlt);
|
library.addIcons(faExchangeAlt);
|
||||||
library.addIcons(faAngleDoubleUp);
|
library.addIcons(faAngleDoubleUp);
|
||||||
library.addIcons(faAngleDoubleDown);
|
library.addIcons(faAngleDoubleDown);
|
||||||
|
library.addIcons(faChevronDown);
|
||||||
|
library.addIcons(faFileAlt);
|
||||||
|
library.addIcons(faRedoAlt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<h1 style="float: left;" i18n="shared.address">Address</h1>
|
<h1 i18n="shared.address">Address</h1>
|
||||||
<a [routerLink]="['/address/' | relativeUrl, addressString]" style="line-height: 56px; margin-left: 10px;">
|
<span class="address-link">
|
||||||
<span class="d-inline d-lg-none">{{ addressString | shortenString : 24 }}</span>
|
<a [routerLink]="['/address/' | relativeUrl, addressString]">
|
||||||
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
<span class="d-inline d-lg-none">{{ addressString | shortenString : 24 }}</span>
|
||||||
</a>
|
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
||||||
<app-clipboard [text]="addressString"></app-clipboard>
|
</a>
|
||||||
|
<app-clipboard [text]="addressString"></app-clipboard>
|
||||||
|
</span>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
@@ -13,26 +15,26 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col-md">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-received">Total received</td>
|
<td i18n="address.total-received">Total received</td>
|
||||||
<td>{{ totalReceived / 100 | number: '1.2-2' }} BSQ</td>
|
<td>{{ totalReceived / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Total sent</td>
|
<td i18n="address.total-sent">Total sent</td>
|
||||||
<td>{{ totalSent / 100 | number: '1.2-2' }} BSQ</td>
|
<td>{{ totalSent / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.balance">Balance</td>
|
<td i18n="address.balance">Balance</td>
|
||||||
<td>{{ (totalReceived - totalSent) / 100 | number: '1.2-2' }} BSQ (<app-bsq-amount [bsq]="totalReceived - totalSent" [forceFiat]="true" [green]="true"></app-bsq-amount>)</td>
|
<td>{{ (totalReceived - totalSent) / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span> <span class="fiat"><app-bsq-amount [bsq]="totalReceived - totalSent" [forceFiat]="true" [green]="true"></app-bsq-amount></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-100 d-block d-md-none"></div>
|
<div class="w-100 d-block d-md-none"></div>
|
||||||
<div class="col qrcode-col">
|
<div class="col-md qrcode-col">
|
||||||
<div class="qr-wrapper">
|
<div class="qr-wrapper">
|
||||||
<app-qrcode [data]="addressString"></app-qrcode>
|
<app-qrcode [data]="addressString"></app-qrcode>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,21 +3,73 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 25px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 576px) {
|
.qrcode-col {
|
||||||
.qrcode-col {
|
text-align: center;
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@media (max-width: 575.98px) {
|
|
||||||
.qrcode-col {
|
.qrcode-col > div {
|
||||||
|
margin: 20px auto 5px;
|
||||||
|
@media (min-width: 768px) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
margin: auto;
|
||||||
|
|
||||||
.qrcode-col > div {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fiat {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
tr td {
|
||||||
|
&:last-child {
|
||||||
|
text-align: right;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-link {
|
||||||
|
line-height: 26px;
|
||||||
|
margin-left: 0px;
|
||||||
|
top: 14px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
line-height: 38px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row{
|
||||||
|
flex-direction: column;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.mobile-bottomcol {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.details-table td:first-child {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { ParamMap, ActivatedRoute } from '@angular/router';
|
|||||||
import { Subscription, of } from 'rxjs';
|
import { Subscription, of } from 'rxjs';
|
||||||
import { BisqTransaction } from '../bisq.interfaces';
|
import { BisqTransaction } from '../bisq.interfaces';
|
||||||
import { BisqApiService } from '../bisq-api.service';
|
import { BisqApiService } from '../bisq-api.service';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bisq-address',
|
selector: 'app-bisq-address',
|
||||||
@@ -22,12 +23,15 @@ export class BisqAddressComponent implements OnInit, OnDestroy {
|
|||||||
totalSent = 0;
|
totalSent = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private bisqApiService: BisqApiService,
|
private bisqApiService: BisqApiService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
this.mainSubscription = this.route.paramMap
|
this.mainSubscription = this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpResponse, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpResponse, HttpParams } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BisqTransaction, BisqBlock, BisqStats } from './bisq.interfaces';
|
import { BisqTransaction, BisqBlock, BisqStats, MarketVolume, Trade, Markets, Tickers, Offers, Currencies, HighLowOpenClose, SummarizedInterval } from './bisq.interfaces';
|
||||||
|
|
||||||
const API_BASE_URL = '/bisq/api';
|
const API_BASE_URL = '/bisq/api';
|
||||||
|
|
||||||
@@ -42,4 +42,37 @@ export class BisqApiService {
|
|||||||
getAddress$(address: string): Observable<BisqTransaction[]> {
|
getAddress$(address: string): Observable<BisqTransaction[]> {
|
||||||
return this.httpClient.get<BisqTransaction[]>(API_BASE_URL + '/address/' + address);
|
return this.httpClient.get<BisqTransaction[]>(API_BASE_URL + '/address/' + address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMarkets$(): Observable<Markets> {
|
||||||
|
return this.httpClient.get<Markets>(API_BASE_URL + '/markets/markets');
|
||||||
|
}
|
||||||
|
|
||||||
|
getMarketsTicker$(): Observable<Tickers> {
|
||||||
|
return this.httpClient.get<Tickers>(API_BASE_URL + '/markets/ticker');
|
||||||
|
}
|
||||||
|
|
||||||
|
getMarketsCurrencies$(): Observable<Currencies> {
|
||||||
|
return this.httpClient.get<Currencies>(API_BASE_URL + '/markets/currencies');
|
||||||
|
}
|
||||||
|
|
||||||
|
getMarketsHloc$(market: string, interval: 'minute' | 'half_hour' | 'hour' | 'half_day' | 'day'
|
||||||
|
| 'week' | 'month' | 'year' | 'auto'): Observable<SummarizedInterval[]> {
|
||||||
|
return this.httpClient.get<SummarizedInterval[]>(API_BASE_URL + '/markets/hloc?market=' + market + '&interval=' + interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMarketOffers$(market: string): Observable<Offers> {
|
||||||
|
return this.httpClient.get<Offers>(API_BASE_URL + '/markets/offers?market=' + market);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMarketTrades$(market: string): Observable<Trade[]> {
|
||||||
|
return this.httpClient.get<Trade[]>(API_BASE_URL + '/markets/trades?market=' + market);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMarketVolumesByTime$(period: string): Observable<HighLowOpenClose[]> {
|
||||||
|
return this.httpClient.get<HighLowOpenClose[]>(API_BASE_URL + '/markets/volumes/' + period);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllVolumesDay$(): Observable<MarketVolume[]> {
|
||||||
|
return this.httpClient.get<MarketVolume[]>(API_BASE_URL + '/markets/volumes?interval=week');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
<ng-template [ngIf]="!isLoading && !error">
|
<ng-template [ngIf]="!isLoading && !error">
|
||||||
|
|
||||||
<div class="box">
|
<div class="box block-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ block.time | date:'yyyy-MM-dd HH:mm' }}
|
{{ block.time | date:'yyyy-MM-dd HH:mm' }}
|
||||||
<div class="lg-inline">
|
<div class="lg-inline">
|
||||||
<i>(<app-time-since [time]="block.time / 1000" [fastRender]="true"></app-time-since>)</i>
|
<i class="symbol">(<app-time-since [time]="block.time / 1000" [fastRender]="true"></app-time-since>)</i>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
<ng-template [ngIf]="isLoading && !error">
|
<ng-template [ngIf]="isLoading && !error">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -1,10 +1,44 @@
|
|||||||
|
|
||||||
.td-width {
|
.td-width {
|
||||||
width: 175px;
|
width: 140px;
|
||||||
}
|
@media (min-width: 768px) {
|
||||||
|
width: 175px;
|
||||||
@media (max-width: 767.98px) {
|
}
|
||||||
.td-width {
|
}
|
||||||
width: 140px;
|
|
||||||
|
h1 {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row{
|
||||||
|
flex-direction: column;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-container {
|
||||||
|
.table {
|
||||||
|
tr td {
|
||||||
|
&:last-child {
|
||||||
|
text-align: right;
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fiat {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import { switchMap, catchError } from 'rxjs/operators';
|
|||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
||||||
import { HttpErrorResponse } from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bisq-block',
|
selector: 'app-bisq-block',
|
||||||
@@ -23,6 +24,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy {
|
|||||||
error: HttpErrorResponse | null;
|
error: HttpErrorResponse | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
private bisqApiService: BisqApiService,
|
private bisqApiService: BisqApiService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
@@ -32,6 +34,8 @@ export class BisqBlockComponent implements OnInit, OnDestroy {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
this.subscription = this.route.paramMap
|
this.subscription = this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<h1 style="float: left;" i18n="Bisq blocks header">Blocks</h1>
|
<h1 style="float: left;" i18n="Bisq blocks header">BSQ Blocks</h1>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<tr *ngFor="let block of blocks.value[0]; trackBy: trackByFn">
|
<tr *ngFor="let block of blocks.value[0]; trackBy: trackByFn">
|
||||||
<td><a [routerLink]="['/block/' | relativeUrl, block.hash]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
|
<td><a [routerLink]="['/block/' | relativeUrl, block.hash]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
|
||||||
<td><app-time-since [time]="block.time / 1000" [fastRender]="true"></app-time-since></td>
|
<td><app-time-since [time]="block.time / 1000" [fastRender]="true"></app-time-since></td>
|
||||||
<td>{{ calculateTotalOutput(block) / 100 | number: '1.2-2' }}<span class="d-none d-md-inline"> BSQ</span></td>
|
<td>{{ calculateTotalOutput(block) / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
<td class="d-none d-md-block">{{ block.txs.length }}</td>
|
<td class="d-none d-md-block">{{ block.txs.length }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
<div class="pagination">
|
||||||
<ngb-pagination *ngIf="blocks.value" [size]="paginationSize" [collectionSize]="blocks.value[1]" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true"></ngb-pagination>
|
<ngb-pagination *ngIf="blocks.value" [size]="paginationSize" [collectionSize]="blocks.value[1]" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true"></ngb-pagination>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.pagination {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { Observable } from 'rxjs';
|
|||||||
import { BisqBlock, BisqOutput, BisqTransaction } from '../bisq.interfaces';
|
import { BisqBlock, BisqOutput, BisqTransaction } from '../bisq.interfaces';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bisq-blocks',
|
selector: 'app-bisq-blocks',
|
||||||
@@ -22,9 +23,10 @@ export class BisqBlocksComponent implements OnInit {
|
|||||||
isLoading = true;
|
isLoading = true;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
paginationSize: 'sm' | 'lg' = 'md';
|
paginationSize: 'sm' | 'lg' = 'md';
|
||||||
paginationMaxSize = 10;
|
paginationMaxSize = 4;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
private bisqApiService: BisqApiService,
|
private bisqApiService: BisqApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -32,6 +34,7 @@ export class BisqBlocksComponent implements OnInit {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`);
|
this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`);
|
||||||
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
|
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
|
||||||
this.loadingItems = Array(this.itemsPerPage);
|
this.loadingItems = Array(this.itemsPerPage);
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
<h1 i18n="Bisq markets title">Bisq Trading Volume</h1>
|
||||||
|
|
||||||
|
<div id="volumeHolder">
|
||||||
|
<ng-template #loadingVolumes>
|
||||||
|
<div class="text-center loadingVolumes">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-container *ngIf="volumes$ | async as volumes; else loadingVolumes">
|
||||||
|
<app-lightweight-charts-area [data]="volumes.data" [lineData]="volumes.linesData"></app-lightweight-charts-area>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<div class="container-info">
|
||||||
|
<h1>
|
||||||
|
<ng-template [ngIf]="stateService.env.OFFICIAL_BISQ_MARKETS" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
|
||||||
|
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
|
||||||
|
</h1>
|
||||||
|
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table table-borderless table-striped">
|
||||||
|
<thead>
|
||||||
|
<th><ng-container i18n>Currency</ng-container> <button [disabled]="(sort$ | async) === 'name'" class="btn btn-link btn-sm" (click)="sort('name')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
|
||||||
|
<th i18n>Price</th>
|
||||||
|
<th><ng-container i18n="Trading volume 7D">Volume (7d)</ng-container> <button [disabled]="(sort$ | async) === 'volumes'" class="btn btn-link btn-sm" (click)="sort('volumes')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
|
||||||
|
<th><ng-container i18n="Trades amount 7D">Trades (7d)</ng-container> <button [disabled]="(sort$ | async) === 'trades'" class="btn btn-link btn-sm" (click)="sort('trades')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
|
||||||
|
</thead>
|
||||||
|
<tbody *ngIf="tickers.value; else loadingTmpl">
|
||||||
|
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn;">
|
||||||
|
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.name }})</a></td>
|
||||||
|
<td>
|
||||||
|
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
|
||||||
|
<ng-template #fiat>
|
||||||
|
<span class="green-color">{{ ticker.last | currency: ticker.market.rsymbol }}</span>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-fiat [value]="ticker.volume?.volume"></app-fiat>
|
||||||
|
</td>
|
||||||
|
<td>{{ ticker.volume?.num_trades ? ticker.volume?.num_trades : 0 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<h2 i18n="Latest Trades header">Latest Trades</h2>
|
||||||
|
<app-bisq-trades [trades$]="trades$"></app-bisq-trades>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loadingTmpl>
|
||||||
|
<tr *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<td *ngFor="let j of [1, 2, 3, 4]"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#volumeHolder {
|
||||||
|
height: 500px;
|
||||||
|
background-color: #000;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingVolumes {
|
||||||
|
position: relative;
|
||||||
|
top: 45%;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow: scroll;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
font-size: 13px;
|
||||||
|
@media(min-width: 576px){
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-info{
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
131
frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts
Normal file
131
frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable, combineLatest, BehaviorSubject, of } from 'rxjs';
|
||||||
|
import { map, share, switchMap } from 'rxjs/operators';
|
||||||
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
|
import { StateService } from 'src/app/services/state.service';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
import { BisqApiService } from '../bisq-api.service';
|
||||||
|
import { Trade } from '../bisq.interfaces';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-bisq-dashboard',
|
||||||
|
templateUrl: './bisq-dashboard.component.html',
|
||||||
|
styleUrls: ['./bisq-dashboard.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class BisqDashboardComponent implements OnInit {
|
||||||
|
tickers$: Observable<any>;
|
||||||
|
volumes$: Observable<any>;
|
||||||
|
trades$: Observable<Trade[]>;
|
||||||
|
sort$ = new BehaviorSubject<string>('trades');
|
||||||
|
|
||||||
|
allowCryptoCoins = ['usdc', 'l-btc', 'bsq'];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private bisqApiService: BisqApiService,
|
||||||
|
public stateService: StateService,
|
||||||
|
private seoService: SeoService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.seoService.setTitle(`Markets`);
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
|
this.volumes$ = this.bisqApiService.getAllVolumesDay$()
|
||||||
|
.pipe(
|
||||||
|
map((volumes) => {
|
||||||
|
const data = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
time: volume.period_start,
|
||||||
|
value: volume.volume,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const linesData = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
time: volume.period_start,
|
||||||
|
value: volume.num_trades,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
linesData: linesData,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMarkets = this.bisqApiService.getMarkets$().pipe(share());
|
||||||
|
|
||||||
|
this.tickers$ = combineLatest([
|
||||||
|
this.bisqApiService.getMarketsTicker$(),
|
||||||
|
getMarkets,
|
||||||
|
this.bisqApiService.getMarketVolumesByTime$('7d'),
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
map(([tickers, markets, volumes]) => {
|
||||||
|
|
||||||
|
const newTickers = [];
|
||||||
|
for (const t in tickers) {
|
||||||
|
|
||||||
|
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||||
|
const pair = t.split('_');
|
||||||
|
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedTicker: any = tickers[t];
|
||||||
|
|
||||||
|
mappedTicker.pair_url = t;
|
||||||
|
mappedTicker.pair = t.replace('_', '/').toUpperCase();
|
||||||
|
mappedTicker.market = markets[t];
|
||||||
|
mappedTicker.volume = volumes[t];
|
||||||
|
mappedTicker.name = `${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lname : mappedTicker.market.rname} (${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lsymbol : mappedTicker.market.rsymbol}`;
|
||||||
|
newTickers.push(mappedTicker);
|
||||||
|
}
|
||||||
|
return newTickers;
|
||||||
|
}),
|
||||||
|
switchMap((tickers) => combineLatest([this.sort$, of(tickers)])),
|
||||||
|
map(([sort, tickers]) => {
|
||||||
|
if (sort === 'trades') {
|
||||||
|
tickers.sort((a, b) => (b.volume && b.volume.num_trades || 0) - (a.volume && a.volume.num_trades || 0));
|
||||||
|
} else if (sort === 'volumes') {
|
||||||
|
tickers.sort((a, b) => (b.volume && b.volume.volume || 0) - (a.volume && a.volume.volume || 0));
|
||||||
|
} else if (sort === 'name') {
|
||||||
|
tickers.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
return tickers;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.trades$ = combineLatest([
|
||||||
|
this.bisqApiService.getMarketTrades$('all'),
|
||||||
|
getMarkets,
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
map(([trades, markets]) => {
|
||||||
|
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||||
|
trades = trades.filter((trade) => {
|
||||||
|
const pair = trade.market.split('_');
|
||||||
|
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return trades.map((trade => {
|
||||||
|
trade._market = markets[trade.market];
|
||||||
|
return trade;
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackByFn(index: number) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(by: string) {
|
||||||
|
this.sort$.next(by);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
<router-outlet></router-outlet>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-bisq-explorer',
|
|
||||||
templateUrl: './bisq-explorer.component.html',
|
|
||||||
styleUrls: ['./bisq-explorer.component.scss']
|
|
||||||
})
|
|
||||||
export class BisqExplorerComponent implements OnInit {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private websocketService: WebsocketService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.websocketService.want(['blocks']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="row row-cols-1 row-cols-md-2">
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title" i18n="bisq-dashboard.price-index-title">Bisq Price Index</h5>
|
||||||
|
<div class="big-fiat">
|
||||||
|
<span *ngIf="usdPrice$ | async as usdPrice; else loading">
|
||||||
|
<span [appColoredPrice]="usdPrice">{{ usdPrice | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title" i18n="bisq-dashboard.market-price-title">Bisq Market Price</h5>
|
||||||
|
<div class="big-fiat">
|
||||||
|
<span class="green-color" *ngIf="bisqMarketPrice; else loading">
|
||||||
|
<span [appColoredPrice]="bisqMarketPrice">{{ bisqMarketPrice | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-cols-1 row-cols-md-2">
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">US Dollar - BTC/USD</h5>
|
||||||
|
<div class="chart-container">
|
||||||
|
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
|
||||||
|
<app-lightweight-charts [height]="300" [data]="hlocData.hloc" [volumeData]="hlocData.volume" [precision]="2"></app-lightweight-charts>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title" i18n="Bisq markets title">Bisq Trading Volume</h5>
|
||||||
|
<div class="chart-container">
|
||||||
|
<ng-container *ngIf="volumes$ | async as volumes; else loadingSpinner">
|
||||||
|
<app-lightweight-charts-area [height]="300" [data]="volumes.data" [lineData]="volumes.linesData"></app-lightweight-charts-area>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-cols-1 row-cols-md-2">
|
||||||
|
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
|
||||||
|
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-center">
|
||||||
|
<ng-template [ngIf]="stateService.env.OFFICIAL_BISQ_MARKETS" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
|
||||||
|
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table table-borderless table-striped">
|
||||||
|
<thead>
|
||||||
|
<th><ng-container i18n>Currency</ng-container> <button [disabled]="(sort$ | async) === 'name'" class="btn btn-link btn-sm" (click)="sort('name')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
|
||||||
|
<th i18n>Price</th>
|
||||||
|
<th><ng-container i18n="Trades amount 7D">Trades (7d)</ng-container> <button [disabled]="(sort$ | async) === 'trades'" class="btn btn-link btn-sm" (click)="sort('trades')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
|
||||||
|
</thead>
|
||||||
|
<tbody *ngIf="tickers.value; else loadingTmpl">
|
||||||
|
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn;">
|
||||||
|
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.name }})</a></td>
|
||||||
|
<td>
|
||||||
|
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
|
||||||
|
<ng-template #fiat>
|
||||||
|
<span class="green-color">{{ ticker.last | currency: ticker.market.rsymbol }}</span>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
|
<td>{{ ticker.volume?.num_trades ? ticker.volume?.num_trades : 0 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center"><a href="" [routerLink]="['/markets' | relativeUrl]" i18n="dashboard.view-all">View all »</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-center" i18n="Latest Trades header">Latest Trades</h5>
|
||||||
|
<app-bisq-trades [trades$]="trades$" view="small"></app-bisq-trades>
|
||||||
|
<div class="text-center"><a href="" [routerLink]="['/markets' | relativeUrl]" i18n="dashboard.view-all">View all »</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-language-selector *ngIf="!stateService.env.OFFICIAL_BISQ_MARKETS"></app-language-selector>
|
||||||
|
|
||||||
|
<div class="text-small text-center mt-3">
|
||||||
|
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loadingTmpl>
|
||||||
|
<tr *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<td *ngFor="let j of [1, 2, 3]"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #loadingSpinner>
|
||||||
|
<div class="text-center loadingGraphs">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #loading>
|
||||||
|
<div class="skeleton-loader shorter"></div>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#volumeHolder {
|
||||||
|
height: 500px;
|
||||||
|
background-color: #000;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingGraphs {
|
||||||
|
position: relative;
|
||||||
|
top: 45%;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
font-size: 13px;
|
||||||
|
@media(min-width: 576px){
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-fiat {
|
||||||
|
color: #3bcc49;
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #1d1f31;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: #4a68b9;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-block {
|
||||||
|
float: left;
|
||||||
|
width: 350px;
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #2d3348;
|
||||||
|
height: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-warning {
|
||||||
|
background-color: #b58800 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-loader {
|
||||||
|
max-width: 100%;
|
||||||
|
&.shorter {
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-padding {
|
||||||
|
padding: 1.25rem 2rem 1.25rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph-card {
|
||||||
|
height: 100%;
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
height: 385px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable, combineLatest, BehaviorSubject, of } from 'rxjs';
|
||||||
|
import { map, share, switchMap } from 'rxjs/operators';
|
||||||
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
|
import { StateService } from 'src/app/services/state.service';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
import { BisqApiService } from '../bisq-api.service';
|
||||||
|
import { Trade } from '../bisq.interfaces';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-main-bisq-dashboard',
|
||||||
|
templateUrl: './bisq-main-dashboard.component.html',
|
||||||
|
styleUrls: ['./bisq-main-dashboard.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class BisqMainDashboardComponent implements OnInit {
|
||||||
|
tickers$: Observable<any>;
|
||||||
|
volumes$: Observable<any>;
|
||||||
|
trades$: Observable<Trade[]>;
|
||||||
|
sort$ = new BehaviorSubject<string>('trades');
|
||||||
|
hlocData$: Observable<any>;
|
||||||
|
usdPrice$: Observable<number>;
|
||||||
|
isLoadingGraph = true;
|
||||||
|
bisqMarketPrice = 0;
|
||||||
|
|
||||||
|
allowCryptoCoins = ['usdc', 'l-btc', 'bsq'];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private bisqApiService: BisqApiService,
|
||||||
|
public stateService: StateService,
|
||||||
|
private seoService: SeoService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.seoService.setTitle(`Markets`);
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
|
this.usdPrice$ = this.stateService.conversions$.asObservable().pipe(
|
||||||
|
map((conversions) => conversions.USD)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.volumes$ = this.bisqApiService.getAllVolumesDay$()
|
||||||
|
.pipe(
|
||||||
|
map((volumes) => {
|
||||||
|
const data = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
time: volume.period_start,
|
||||||
|
value: volume.volume,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const linesData = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
time: volume.period_start,
|
||||||
|
value: volume.num_trades,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
linesData: linesData,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMarkets = this.bisqApiService.getMarkets$().pipe(share());
|
||||||
|
|
||||||
|
this.tickers$ = combineLatest([
|
||||||
|
this.bisqApiService.getMarketsTicker$(),
|
||||||
|
getMarkets,
|
||||||
|
this.bisqApiService.getMarketVolumesByTime$('7d'),
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
map(([tickers, markets, volumes]) => {
|
||||||
|
|
||||||
|
const newTickers = [];
|
||||||
|
for (const t in tickers) {
|
||||||
|
|
||||||
|
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||||
|
const pair = t.split('_');
|
||||||
|
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedTicker: any = tickers[t];
|
||||||
|
|
||||||
|
mappedTicker.pair_url = t;
|
||||||
|
mappedTicker.pair = t.replace('_', '/').toUpperCase();
|
||||||
|
mappedTicker.market = markets[t];
|
||||||
|
mappedTicker.volume = volumes[t];
|
||||||
|
mappedTicker.name = `${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lname : mappedTicker.market.rname} (${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lsymbol : mappedTicker.market.rsymbol}`;
|
||||||
|
newTickers.push(mappedTicker);
|
||||||
|
}
|
||||||
|
return newTickers;
|
||||||
|
}),
|
||||||
|
switchMap((tickers) => combineLatest([this.sort$, of(tickers)])),
|
||||||
|
map(([sort, tickers]) => {
|
||||||
|
if (sort === 'trades') {
|
||||||
|
tickers.sort((a, b) => (b.volume && b.volume.num_trades || 0) - (a.volume && a.volume.num_trades || 0));
|
||||||
|
} else if (sort === 'volumes') {
|
||||||
|
tickers.sort((a, b) => (b.volume && b.volume.volume || 0) - (a.volume && a.volume.volume || 0));
|
||||||
|
} else if (sort === 'name') {
|
||||||
|
tickers.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
return tickers.slice(0, 10);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.trades$ = combineLatest([
|
||||||
|
this.bisqApiService.getMarketTrades$('all'),
|
||||||
|
getMarkets,
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
map(([trades, markets]) => {
|
||||||
|
if (!this.stateService.env.OFFICIAL_BISQ_MARKETS) {
|
||||||
|
trades = trades.filter((trade) => {
|
||||||
|
const pair = trade.market.split('_');
|
||||||
|
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return trades.map((trade => {
|
||||||
|
trade._market = markets[trade.market];
|
||||||
|
return trade;
|
||||||
|
})).slice(0, 10);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.hlocData$ = this.bisqApiService.getMarketsHloc$('btc_usd', 'day')
|
||||||
|
.pipe(
|
||||||
|
map((hlocData) => {
|
||||||
|
this.isLoadingGraph = false;
|
||||||
|
|
||||||
|
hlocData = hlocData.map((h) => {
|
||||||
|
h.time = h.period_start;
|
||||||
|
return h;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hlocVolume = hlocData.map((h) => {
|
||||||
|
return {
|
||||||
|
time: h.time,
|
||||||
|
value: h.volume_right,
|
||||||
|
color: h.close > h.avg ? 'rgba(0, 41, 74, 0.7)' : 'rgba(0, 41, 74, 1)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add whitespace
|
||||||
|
if (hlocData.length > 1) {
|
||||||
|
const newHloc = [];
|
||||||
|
newHloc.push(hlocData[0]);
|
||||||
|
|
||||||
|
const period = 86400;
|
||||||
|
let periods = 0;
|
||||||
|
const startingDate = hlocData[0].period_start;
|
||||||
|
let index = 1;
|
||||||
|
while (true) {
|
||||||
|
periods++;
|
||||||
|
if (hlocData[index].period_start > startingDate + period * periods) {
|
||||||
|
newHloc.push({
|
||||||
|
time: startingDate + period * periods,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
newHloc.push(hlocData[index]);
|
||||||
|
index++;
|
||||||
|
if (!hlocData[index]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hlocData = newHloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bisqMarketPrice = hlocData[hlocData.length - 1].close;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hloc: hlocData,
|
||||||
|
volume: hlocVolume,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackByFn(index: number) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(by: string) {
|
||||||
|
this.sort$.next(by);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
112
frontend/src/app/bisq/bisq-market/bisq-market.component.html
Normal file
112
frontend/src/app/bisq/bisq-market/bisq-market.component.html
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
|
||||||
|
|
||||||
|
<ng-container *ngIf="currency$ | async as currency; else loadingSpinner">
|
||||||
|
<h1>{{ currency.market.rtype === 'crypto' ? currency.market.lname : currency.market.rname }} - {{ currency.pair }}</h1>
|
||||||
|
<div class="priceheader">
|
||||||
|
<ng-container *ngIf="currency.market.rtype === 'fiat'; else headerPriceCrypto"><span class="green-color">{{ hlocData.hloc[hlocData.hloc.length - 1].close | currency: currency.market.rsymbol }}</span></ng-container>
|
||||||
|
<ng-template #headerPriceCrypto>{{ hlocData.hloc[hlocData.hloc.length - 1].close | number: '1.' + currency.market.rprecision + '-' + currency.market.rprecision }} {{ currency.market.rsymbol }}</ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="radioGroupForm" class="mb-3 radio-form">
|
||||||
|
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="interval">
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'half_hour'" (click)="setFragment('half_hour')"> 30M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'hour'" (click)="setFragment('hour')"> 1H
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'half_day'" (click)="setFragment('half_day')"> 12H
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'day'" (click)="setFragment('day')"> 1D
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'week'" (click)="setFragment('week')"> 1W
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'month'" (click)="setFragment('month')"> 1M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
|
<input ngbButton type="radio" [value]="'year'" (click)="setFragment('year')"> 1Y
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<div id="graphHolder">
|
||||||
|
<div class="text-center loadingChart" [hidden]="!isLoadingGraph">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
<app-lightweight-charts [data]="hlocData.hloc" [volumeData]="hlocData.volume" [precision]="currency.market.rtype === 'crypto' ? currency.market.lprecision : currency.market.rprecision"></app-lightweight-charts>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<ng-container *ngIf="offers$ | async as offers; else loadingSpinner">
|
||||||
|
<div class="row row-cols-1 row-cols-md-2">
|
||||||
|
<ng-container *ngTemplateOutlet="offersList; context: { offers: offers.buys, direction: 'BUY', market: currency.market }"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="offersList; context: { offers: offers.sells, direction: 'SELL', market: currency.market }"></ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<ng-container *ngIf="trades$ | async as trades; else loadingSpinner">
|
||||||
|
<h2 i18n="Latest Trades header">Latest Trades</h2>
|
||||||
|
|
||||||
|
<app-bisq-trades [trades$]="trades$" [market]="currency.market"></app-bisq-trades>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #offersList let-offers="offers" let-direction="direction", let-market="market">
|
||||||
|
<div class="col">
|
||||||
|
<h2>
|
||||||
|
<ng-template [ngIf]="direction === 'BUY'" [ngIfElse]="sellOffers" i18n="Bisq Buy Offers">Buy Offers</ng-template>
|
||||||
|
<ng-template #sellOffers i18n="Bisq Sell Offers">Sell Offers</ng-template>
|
||||||
|
</h2>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table table-borderless table-striped">
|
||||||
|
<thead>
|
||||||
|
<th i18n>Price</th>
|
||||||
|
<th><ng-container *ngTemplateOutlet="amount; context: {$implicit: market.lsymbol }"></ng-container></th>
|
||||||
|
<th><ng-container *ngTemplateOutlet="amount; context: {$implicit: market.rsymbol }"></ng-container></th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let offer of offers">
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="market.rtype === 'fiat'; else priceCrypto"><span class="green-color">{{ offer.price | currency: market.rsymbol }}</span></ng-container>
|
||||||
|
<ng-template #priceCrypto>{{ offer.price | number: '1.2-' + market.rprecision }} <span class="symbol">{{ market.rsymbol }}</span></ng-template>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="market.ltype === 'fiat'; else amountCrypto"><span class="green-color">{{ offer.amount | currency: market.rsymbol }}</span></ng-container>
|
||||||
|
<ng-template #amountCrypto>{{ offer.amount | number: '1.2-' + market.lprecision }} <span class="symbol">{{ market.lsymbol }}</span></ng-template>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="market.rtype === 'fiat'; else volumeCrypto"><span class="green-color">{{ offer.volume | currency: market.rsymbol }}</span></ng-container>
|
||||||
|
<ng-template #volumeCrypto>{{ offer.volume | number: '1.2-' + market.rprecision }} <span class="symbol">{{ market.rsymbol }}</span></ng-template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #loadingSpinner>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #amount let-i i18n="Trade amount (Symbol)">Amount ({{ i }})</ng-template>
|
||||||
46
frontend/src/app/bisq/bisq-market/bisq-market.component.scss
Normal file
46
frontend/src/app/bisq/bisq-market/bisq-market.component.scss
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
.priceheader {
|
||||||
|
font-size: 24px;
|
||||||
|
@media(min-width: 576px){
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-form {
|
||||||
|
@media(min-width: 576px){
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingChart {
|
||||||
|
z-index: 100;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#graphHolder {
|
||||||
|
height: 550px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
&:last-child{
|
||||||
|
margin-top: 50px;
|
||||||
|
@media(min-width: 576px){
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow: scroll;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
font-size: 13px;
|
||||||
|
@media(min-width: 576px){
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
158
frontend/src/app/bisq/bisq-market/bisq-market.component.ts
Normal file
158
frontend/src/app/bisq/bisq-market/bisq-market.component.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { combineLatest, merge, Observable, of } from 'rxjs';
|
||||||
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
import { BisqApiService } from '../bisq-api.service';
|
||||||
|
import { OffersMarket, Trade } from '../bisq.interfaces';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-bisq-market',
|
||||||
|
templateUrl: './bisq-market.component.html',
|
||||||
|
styleUrls: ['./bisq-market.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class BisqMarketComponent implements OnInit, OnDestroy {
|
||||||
|
hlocData$: Observable<any>;
|
||||||
|
currency$: Observable<any>;
|
||||||
|
offers$: Observable<OffersMarket>;
|
||||||
|
trades$: Observable<Trade[]>;
|
||||||
|
radioGroupForm: FormGroup;
|
||||||
|
defaultInterval = 'day';
|
||||||
|
|
||||||
|
isLoadingGraph = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private bisqApiService: BisqApiService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private seoService: SeoService,
|
||||||
|
private router: Router,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.radioGroupForm = this.formBuilder.group({
|
||||||
|
interval: [this.defaultInterval],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (['half_hour', 'hour', 'half_day', 'day', 'week', 'month', 'year', 'auto'].indexOf(this.route.snapshot.fragment) > -1) {
|
||||||
|
this.radioGroupForm.controls.interval.setValue(this.route.snapshot.fragment, { emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currency$ = this.bisqApiService.getMarkets$()
|
||||||
|
.pipe(
|
||||||
|
switchMap((markets) => combineLatest([of(markets), this.route.paramMap])),
|
||||||
|
map(([markets, routeParams]) => {
|
||||||
|
const pair = routeParams.get('pair');
|
||||||
|
const pairUpperCase = pair.replace('_', '/').toUpperCase();
|
||||||
|
this.seoService.setTitle(`Bisq market: ${pairUpperCase}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pair: pairUpperCase,
|
||||||
|
market: markets[pair],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.trades$ = this.route.paramMap
|
||||||
|
.pipe(
|
||||||
|
map(routeParams => routeParams.get('pair')),
|
||||||
|
switchMap((marketPair) => this.bisqApiService.getMarketTrades$(marketPair)),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.offers$ = this.route.paramMap
|
||||||
|
.pipe(
|
||||||
|
map(routeParams => routeParams.get('pair')),
|
||||||
|
switchMap((marketPair) => this.bisqApiService.getMarketOffers$(marketPair)),
|
||||||
|
map((offers) => offers[Object.keys(offers)[0]])
|
||||||
|
);
|
||||||
|
|
||||||
|
this.hlocData$ = combineLatest([
|
||||||
|
this.route.paramMap,
|
||||||
|
merge(this.radioGroupForm.get('interval').valueChanges, of(this.radioGroupForm.get('interval').value)),
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
switchMap(([routeParams, interval]) => {
|
||||||
|
this.isLoadingGraph = true;
|
||||||
|
const pair = routeParams.get('pair');
|
||||||
|
return this.bisqApiService.getMarketsHloc$(pair, interval);
|
||||||
|
}),
|
||||||
|
map((hlocData) => {
|
||||||
|
this.isLoadingGraph = false;
|
||||||
|
|
||||||
|
hlocData = hlocData.map((h) => {
|
||||||
|
h.time = h.period_start;
|
||||||
|
return h;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hlocVolume = hlocData.map((h) => {
|
||||||
|
return {
|
||||||
|
time: h.time,
|
||||||
|
value: h.volume_right,
|
||||||
|
color: h.close > h.avg ? 'rgba(0, 41, 74, 0.7)' : 'rgba(0, 41, 74, 1)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add whitespace
|
||||||
|
if (hlocData.length > 1) {
|
||||||
|
const newHloc = [];
|
||||||
|
newHloc.push(hlocData[0]);
|
||||||
|
|
||||||
|
const period = this.getUnixTimestampFromInterval(this.radioGroupForm.get('interval').value); // temp
|
||||||
|
let periods = 0;
|
||||||
|
const startingDate = hlocData[0].period_start;
|
||||||
|
let index = 1;
|
||||||
|
while (true) {
|
||||||
|
periods++;
|
||||||
|
if (hlocData[index].period_start > startingDate + period * periods) {
|
||||||
|
newHloc.push({
|
||||||
|
time: startingDate + period * periods,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
newHloc.push(hlocData[index]);
|
||||||
|
index++;
|
||||||
|
if (!hlocData[index]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hlocData = newHloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hloc: hlocData,
|
||||||
|
volume: hlocVolume,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFragment(fragment: string) {
|
||||||
|
this.router.navigate([], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
fragment: fragment
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.websocketService.stopTrackingBisqMarket();
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnixTimestampFromInterval(interval: string): number {
|
||||||
|
switch (interval) {
|
||||||
|
case 'minute': return 60;
|
||||||
|
case 'half_hour': return 1800;
|
||||||
|
case 'hour': return 3600;
|
||||||
|
case 'half_day': return 43200;
|
||||||
|
case 'day': return 86400;
|
||||||
|
case 'week': return 604800;
|
||||||
|
case 'month': return 2592000;
|
||||||
|
case 'year': return 31579200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,20 +5,20 @@
|
|||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-md">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody *ngIf="!isLoading; else loadingTemplate">
|
<tbody *ngIf="!isLoading; else loadingTemplate">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="BSQ existing amount">Existing amount</td>
|
<td class="td-width" i18n="BSQ existing amount">Existing amount</td>
|
||||||
<td>{{ (stats.minted - stats.burnt) | number: '1.2-2' }} BSQ</td>
|
<td>{{ (stats.minted - stats.burnt) | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="BSQ minted amount">Minted amount</td>
|
<td i18n="BSQ minted amount">Minted amount</td>
|
||||||
<td>{{ stats.minted | number: '1.2-2' }} BSQ</td>
|
<td>{{ stats.minted | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="BSQ burnt amount">Burnt amount</td>
|
<td i18n="BSQ burnt amount">Burnt amount</td>
|
||||||
<td>{{ stats.burnt | number: '1.2-2' }} BSQ</td>
|
<td>{{ stats.burnt | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="BSQ addresses">Addresses</td>
|
<td i18n="BSQ addresses">Addresses</td>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<td>{{ stats.spent_txos | number }}</td>
|
<td>{{ stats.spent_txos | number }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="BSQ token price">Price</td>
|
<td i18n>Price</td>
|
||||||
<td><app-fiat [value]="price"></app-fiat></td>
|
<td><app-fiat [value]="price"></app-fiat></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm"></div>
|
<div class="col-md"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,3 +7,12 @@
|
|||||||
width: 175px;
|
width: 175px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fiat {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { BisqApiService } from '../bisq-api.service';
|
|||||||
import { BisqStats } from '../bisq.interfaces';
|
import { BisqStats } from '../bisq.interfaces';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bisq-stats',
|
selector: 'app-bisq-stats',
|
||||||
@@ -15,12 +16,15 @@ export class BisqStatsComponent implements OnInit {
|
|||||||
price: number;
|
price: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
private bisqApiService: BisqApiService,
|
private bisqApiService: BisqApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`);
|
this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`);
|
||||||
this.stateService.bsqPrice$
|
this.stateService.bsqPrice$
|
||||||
.subscribe((bsqPrice) => {
|
.subscribe((bsqPrice) => {
|
||||||
|
|||||||
46
frontend/src/app/bisq/bisq-trades/bisq-trades.component.html
Normal file
46
frontend/src/app/bisq/bisq-trades/bisq-trades.component.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="table-container">
|
||||||
|
<table class="table table-borderless table-striped">
|
||||||
|
<thead>
|
||||||
|
<th i18n>Date</th>
|
||||||
|
<th *ngIf="view === 'all'" i18n>Price</th>
|
||||||
|
<th><ng-container *ngTemplateOutlet="amount; context: {$implicit: 'BTC' }"></ng-container></th>
|
||||||
|
<th>
|
||||||
|
<ng-template [ngIf]="market" [ngIfElse]="noMarket"><ng-container *ngTemplateOutlet="amount; context: {$implicit: market.lsymbol === 'BTC' ? market.rsymbol : market.lsymbol }"></ng-container></ng-template>
|
||||||
|
<ng-template #noMarket i18n>Amount</ng-template>
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tbody *ngIf="(trades$ | async) as trades; else loadingTmpl">
|
||||||
|
<tr *ngFor="let trade of trades;">
|
||||||
|
<td>
|
||||||
|
{{ 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>
|
||||||
|
<ng-template #priceCrypto>{{ trade.price | number: '1.2-' + (trade._market || market).rprecision }} <span class="symbol">{{ (trade._market || market).rsymbol }}</span></ng-template>
|
||||||
|
</td>
|
||||||
|
<ng-container *ngTemplateOutlet="(trade._market || market).rsymbol === 'BTC' ? tradeVolume : tradeAmount"></ng-container>
|
||||||
|
<ng-container *ngTemplateOutlet="(trade._market || market).rsymbol === 'BTC' ? tradeAmount : tradeVolume"></ng-container>
|
||||||
|
<ng-template #tradeAmount>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="(trade._market || market).ltype === 'fiat'; else amountCrypto"><span class="green-color">{{ trade.amount | currency: (trade._market || market).rsymbol }}</span></ng-container>
|
||||||
|
<ng-template #amountCrypto>{{ trade.amount | number: '1.2-' + (trade._market || market).lprecision }} <span class="symbol">{{ (trade._market || market).lsymbol }}</span></ng-template>
|
||||||
|
</td>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #tradeVolume>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="(trade._market || market).rtype === 'fiat'; else volumeCrypto"><span class="green-color">{{ trade.volume | currency: (trade._market || market).rsymbol }}</span></ng-container>
|
||||||
|
<ng-template #volumeCrypto>{{ trade.volume | number: '1.2-' + (trade._market || market).rprecision }} <span class="symbol">{{ (trade._market || market).rsymbol }}</span></ng-template>
|
||||||
|
</td>
|
||||||
|
</ng-template>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loadingTmpl>
|
||||||
|
<tr *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<td *ngFor="let j of loadingColumns"><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #amount let-i i18n="Trade amount (Symbol)">Amount ({{ i }})</ng-template>
|
||||||
12
frontend/src/app/bisq/bisq-trades/bisq-trades.component.scss
Normal file
12
frontend/src/app/bisq/bisq-trades/bisq-trades.component.scss
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.table-container {
|
||||||
|
overflow: scroll;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
font-size: 13px;
|
||||||
|
@media(min-width: 576px){
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
frontend/src/app/bisq/bisq-trades/bisq-trades.component.ts
Normal file
22
frontend/src/app/bisq/bisq-trades/bisq-trades.component.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-bisq-trades',
|
||||||
|
templateUrl: './bisq-trades.component.html',
|
||||||
|
styleUrls: ['./bisq-trades.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class BisqTradesComponent implements OnChanges {
|
||||||
|
@Input() trades$: Observable<any>;
|
||||||
|
@Input() market: any;
|
||||||
|
@Input() view: 'all' | 'small' = 'all';
|
||||||
|
|
||||||
|
loadingColumns = [1, 2, 3, 4];
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
if (this.view === 'small') {
|
||||||
|
this.loadingColumns = [1, 2, 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-md">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="transaction.inputs">Inputs</td>
|
<td class="td-width" i18n="transaction.inputs">Inputs</td>
|
||||||
<td>{{ totalInput / 100 | number: '1.2-2' }} BSQ</td>
|
<td>{{ totalInput / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="transaction.outputs">Outputs</td>
|
<td i18n="transaction.outputs">Outputs</td>
|
||||||
<td>{{ totalOutput / 100 | number: '1.2-2' }} BSQ</td>
|
<td>{{ totalOutput / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="asset.issued-amount|Liquid Asset issued amount">Issued amount</td>
|
<td i18n="asset.issued-amount|Liquid Asset issued amount">Issued amount</td>
|
||||||
<td>{{ totalIssued / 100 | number: '1.2-2' }} BSQ</td>
|
<td>{{ totalIssued / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-md">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody class="mobile-even">
|
<tbody class="mobile-even">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -9,3 +9,14 @@
|
|||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
tr td {
|
||||||
|
&:last-child{
|
||||||
|
text-align: right;
|
||||||
|
@media(min-width: 768px){
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
|
|
||||||
<h1 class="float-left mr-3 mb-md-3" i18n="shared.transaction">Transaction</h1>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="!isLoading && !error">
|
<ng-template [ngIf]="!isLoading && !error">
|
||||||
|
|
||||||
<button *ngIf="(latestBlock$ | async) as latestBlock" type="button" class="btn btn-sm btn-success float-right mr-2 mt-1 mt-md-3">
|
<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>
|
<ng-container *ngTemplateOutlet="latestBlock.height - bisqTx.blockHeight + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - bisqTx.blockHeight + 1}"></ng-container>
|
||||||
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
||||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
||||||
</button>
|
</button>
|
||||||
<div>
|
|
||||||
<a [routerLink]="['/bisq-tx' | relativeUrl, bisqTx.id]" style="line-height: 56px;">
|
|
||||||
|
<h1 i18n="shared.transaction">Transaction</h1>
|
||||||
|
|
||||||
|
<div class="tx-link">
|
||||||
|
<a [routerLink]="['/bisq-tx' | relativeUrl, bisqTx.id]">
|
||||||
<span class="d-inline d-lg-none">{{ bisqTx.id | shortenString : 24 }}</span>
|
<span class="d-inline d-lg-none">{{ bisqTx.id | shortenString : 24 }}</span>
|
||||||
<span class="d-none d-lg-inline">{{ bisqTx.id }}</span>
|
<span class="d-none d-lg-inline">{{ bisqTx.id }}</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -18,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box transaction-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ bisqTx.time | date:'yyyy-MM-dd HH:mm' }}
|
{{ bisqTx.time | date:'yyyy-MM-dd HH:mm' }}
|
||||||
<div class="lg-inline">
|
<div class="lg-inline">
|
||||||
<i>(<app-time-since [time]="bisqTx.time / 1000" [fastRender]="true"></app-time-since>)</i>
|
<i class="symbol">(<app-time-since [time]="bisqTx.time / 1000" [fastRender]="true"></app-time-since>)</i>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -56,12 +58,12 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="BSQ burnt amount">Burnt amount</td>
|
<td class="td-width" i18n="BSQ burnt amount">Burnt amount</td>
|
||||||
<td>
|
<td>
|
||||||
{{ bisqTx.burntFee / 100 | number: '1.2-2' }} BSQ (<app-bsq-amount [bsq]="bisqTx.burntFee" [forceFiat]="true" [green]="true"></app-bsq-amount>)
|
{{ bisqTx.burntFee / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span> <span class="fiat"><app-bsq-amount [bsq]="bisqTx.burntFee" [forceFiat]="true" [green]="true"></app-bsq-amount></span>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
|
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
|
||||||
<td *ngIf="!isLoadingTx; else loadingTxFee">
|
<td *ngIf="!isLoadingTx; else loadingTxFee">
|
||||||
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} sat/vB
|
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span class="symbol">sat/vB</span>
|
||||||
|
|
||||||
<app-tx-fee-rating [tx]="tx"></app-tx-fee-rating>
|
<app-tx-fee-rating [tx]="tx"></app-tx-fee-rating>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,9 +1,126 @@
|
|||||||
.td-width {
|
.adjust-btn-padding {
|
||||||
width: 175px;
|
padding: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-block {
|
||||||
|
padding-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
.td-width {
|
.mobile-bottomcol {
|
||||||
width: 150px;
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.td-width {
|
||||||
|
width: 150px;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small-height {
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-green {
|
||||||
|
color: #1a9436;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-red {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
float: right;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-link {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-top: 40px;
|
||||||
|
position: absolute;
|
||||||
|
@media (min-width: 700px) {
|
||||||
|
margin-top: 14px;
|
||||||
|
margin-left: 10px;
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
top: 14px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-xl {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row{
|
||||||
|
flex-direction: column;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.mobile-bottomcol {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.details-table td:first-child {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiat {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
@media (min-width: 576px){
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1{
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
float: left;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
@media (min-width: 375px){
|
||||||
|
margin-top: 0px;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px){
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transaction-container {
|
||||||
|
font-size: 14px;
|
||||||
|
@media (min-width: 576px){
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
tr td {
|
||||||
|
&:last-child{
|
||||||
|
text-align: right;
|
||||||
|
@media (min-width: 768px){
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import { BisqApiService } from '../bisq-api.service';
|
|||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
||||||
import { HttpErrorResponse } from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bisq-transaction',
|
selector: 'app-bisq-transaction',
|
||||||
@@ -27,6 +28,7 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
|
|||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private bisqApiService: BisqApiService,
|
private bisqApiService: BisqApiService,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
@@ -36,6 +38,8 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
this.subscription = this.route.paramMap.pipe(
|
this.subscription = this.route.paramMap.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<h1 style="float: left;" i18n>Transactions</h1>
|
<h1 style="float: left;" i18n>BSQ Transactions</h1>
|
||||||
|
|
||||||
<div class="d-block float-right">
|
<div class="d-block float-right">
|
||||||
<form [formGroup]="radioGroupForm">
|
<form [formGroup]="radioGroupForm">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<th style="width: 20%;" i18n>Transaction</th>
|
<th style="width: 20%;" i18n>TXID</th>
|
||||||
<th class="d-none d-md-block" style="width: 100%;" i18n>Type</th>
|
<th class="d-none d-md-block" style="width: 100%;" i18n>Type</th>
|
||||||
<th style="width: 20%;" i18n>Amount</th>
|
<th style="width: 20%;" i18n>Amount</th>
|
||||||
<th style="width: 20%;" i18n>Confirmed</th>
|
<th style="width: 20%;" i18n>Confirmed</th>
|
||||||
@@ -26,15 +26,15 @@
|
|||||||
<td><a [routerLink]="['/tx/' | relativeUrl, tx.id]" [state]="{ data: tx }">{{ tx.id | slice : 0 : 8 }}</a></td>
|
<td><a [routerLink]="['/tx/' | relativeUrl, tx.id]" [state]="{ data: tx }">{{ tx.id | slice : 0 : 8 }}</a></td>
|
||||||
<td class="d-none d-md-block">
|
<td class="d-none d-md-block">
|
||||||
<app-bisq-icon class="mr-1" [txType]="tx.txType"></app-bisq-icon>
|
<app-bisq-icon class="mr-1" [txType]="tx.txType"></app-bisq-icon>
|
||||||
<span class="d-none d-md-inline"> {{ tx.txTypeDisplayString }}</span>
|
<span class="d-none d-md-inline"> {{ getStringByTxType(tx.txType) }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<app-bisq-icon class="d-inline d-md-none mr-1" [txType]="tx.txType"></app-bisq-icon>
|
<app-bisq-icon class="d-inline d-md-none mr-1" [txType]="tx.txType"></app-bisq-icon>
|
||||||
<ng-template [ngIf]="tx.txType === 'PAY_TRADE_FEE' || tx.txType === 'ASSET_LISTING_FEE'" [ngIfElse]="defaultTxType">
|
<ng-template [ngIf]="tx.txType === 'PAY_TRADE_FEE' || tx.txType === 'ASSET_LISTING_FEE'" [ngIfElse]="defaultTxType">
|
||||||
{{ tx.burntFee / 100 | number: '1.2-2' }}<span class="d-none d-md-inline"> BSQ</span>
|
{{ tx.burntFee / 100 | number: '1.2-2' }} <span class="d-none d-md-inline symbol">BSQ</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #defaultTxType>
|
<ng-template #defaultTxType>
|
||||||
{{ calculateTotalOutput(tx.outputs) / 100 | number: '1.2-2' }}<span class="d-none d-md-inline"> BSQ</span>
|
{{ calculateTotalOutput(tx.outputs) / 100 | number: '1.2-2' }} <span class="d-none d-md-inline symbol">BSQ</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td><app-time-since [time]="tx.time / 1000" [fastRender]="true"></app-time-since></td>
|
<td><app-time-since [time]="tx.time / 1000" [fastRender]="true"></app-time-since></td>
|
||||||
@@ -44,8 +44,9 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
<div class="pagination">
|
||||||
<ngb-pagination *ngIf="transactions.value" [size]="paginationSize" [collectionSize]="transactions.value[1]" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true"></ngb-pagination>
|
<ngb-pagination *ngIf="transactions.value" [size]="paginationSize" [collectionSize]="transactions.value[1]" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true"></ngb-pagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,3 +7,14 @@ label {
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
left: inherit;
|
left: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1{
|
||||||
|
font-size: 1.5rem;
|
||||||
|
@media(min-width: 375px){
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { SeoService } from 'src/app/services/seo.service';
|
|||||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
|
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bisq-transactions',
|
selector: 'app-bisq-transactions',
|
||||||
@@ -26,19 +27,19 @@ export class BisqTransactionsComponent implements OnInit {
|
|||||||
types: string[] = [];
|
types: string[] = [];
|
||||||
|
|
||||||
txTypeOptions: IMultiSelectOption[] = [
|
txTypeOptions: IMultiSelectOption[] = [
|
||||||
{ id: 1, name: 'Asset listing fee' },
|
{ id: 1, name: $localize`Asset listing fee` },
|
||||||
{ id: 2, name: 'Blind vote' },
|
{ id: 2, name: $localize`Blind vote` },
|
||||||
{ id: 3, name: 'Compensation request' },
|
{ id: 3, name: $localize`Compensation request` },
|
||||||
{ id: 4, name: 'Genesis' },
|
{ id: 4, name: $localize`Genesis` },
|
||||||
{ id: 13, name: 'Irregular' },
|
{ id: 13, name: $localize`Irregular` },
|
||||||
{ id: 5, name: 'Lockup' },
|
{ id: 5, name: $localize`Lockup` },
|
||||||
{ id: 6, name: 'Pay trade fee' },
|
{ id: 6, name: $localize`Pay trade fee` },
|
||||||
{ id: 7, name: 'Proof of burn' },
|
{ id: 7, name: $localize`Proof of burn` },
|
||||||
{ id: 8, name: 'Proposal' },
|
{ id: 8, name: $localize`Proposal` },
|
||||||
{ id: 9, name: 'Reimbursement request' },
|
{ id: 9, name: $localize`Reimbursement request` },
|
||||||
{ id: 10, name: 'Transfer BSQ' },
|
{ id: 10, name: $localize`Transfer BSQ` },
|
||||||
{ id: 11, name: 'Unlock' },
|
{ id: 11, name: $localize`Unlock` },
|
||||||
{ id: 12, name: 'Vote reveal' },
|
{ id: 12, name: $localize`Vote reveal` },
|
||||||
];
|
];
|
||||||
txTypesDefaultChecked = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
txTypesDefaultChecked = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
||||||
|
|
||||||
@@ -59,12 +60,13 @@ export class BisqTransactionsComponent implements OnInit {
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
paginationSize: 'sm' | 'lg' = 'md';
|
paginationSize: 'sm' | 'lg' = 'md';
|
||||||
paginationMaxSize = 10;
|
paginationMaxSize = 4;
|
||||||
|
|
||||||
txTypes = ['ASSET_LISTING_FEE', 'BLIND_VOTE', 'COMPENSATION_REQUEST', 'GENESIS', 'LOCKUP', 'PAY_TRADE_FEE',
|
txTypes = ['ASSET_LISTING_FEE', 'BLIND_VOTE', 'COMPENSATION_REQUEST', 'GENESIS', 'LOCKUP', 'PAY_TRADE_FEE',
|
||||||
'PROOF_OF_BURN', 'PROPOSAL', 'REIMBURSEMENT_REQUEST', 'TRANSFER_BSQ', 'UNLOCK', 'VOTE_REVEAL', 'IRREGULAR'];
|
'PROOF_OF_BURN', 'PROPOSAL', 'REIMBURSEMENT_REQUEST', 'TRANSFER_BSQ', 'UNLOCK', 'VOTE_REVEAL', 'IRREGULAR'];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
private bisqApiService: BisqApiService,
|
private bisqApiService: BisqApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
@@ -74,6 +76,7 @@ export class BisqTransactionsComponent implements OnInit {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
this.seoService.setTitle($localize`:@@add4cd82e3e38a3110fe67b3c7df56e9602644ee:Transactions`);
|
this.seoService.setTitle($localize`:@@add4cd82e3e38a3110fe67b3c7df56e9602644ee:Transactions`);
|
||||||
|
|
||||||
this.radioGroupForm = this.formBuilder.group({
|
this.radioGroupForm = this.formBuilder.group({
|
||||||
@@ -157,6 +160,11 @@ export class BisqTransactionsComponent implements OnInit {
|
|||||||
return outputs.reduce((acc: number, output: BisqOutput) => acc + output.bsqAmount, 0);
|
return outputs.reduce((acc: number, output: BisqOutput) => acc + output.bsqAmount, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStringByTxType(type: string) {
|
||||||
|
const id = this.txTypes.indexOf(type) + 1;
|
||||||
|
return this.txTypeOptions.find((type) => id === type.id).name;
|
||||||
|
}
|
||||||
|
|
||||||
trackByFn(index: number) {
|
trackByFn(index: number) {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,11 +58,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="float-left mt-2-5" *ngIf="showConfirmations && tx.burntFee">
|
<div class="transaction-fee" *ngIf="showConfirmations && tx.burntFee">
|
||||||
<ng-container i18n="BSQ burnt amount">Burnt amount</ng-container>: {{ tx.burntFee / 100 | number: '1.2-2' }} BSQ (<app-bsq-amount [bsq]="tx.burntFee" [forceFiat]="true" [green]="true"></app-bsq-amount>)
|
<ng-container i18n="BSQ burnt amount">Burnt amount</ng-container>: {{ tx.burntFee / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span> <span class="extra-info"><span class="fiat"><app-bsq-amount [bsq]="tx.burntFee" [forceFiat]="true" [green]="true"></app-bsq-amount></span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="float-right">
|
<div class="btn-container">
|
||||||
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
||||||
<button type="button" class="btn btn-sm btn-success mt-2">
|
<button type="button" class="btn btn-sm btn-success mt-2">
|
||||||
<ng-container *ngTemplateOutlet="latestBlock.height - tx.blockHeight + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.blockHeight + 1}"></ng-container>
|
<ng-container *ngTemplateOutlet="latestBlock.height - tx.blockHeight + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.blockHeight + 1}"></ng-container>
|
||||||
|
|||||||
@@ -59,26 +59,81 @@
|
|||||||
background-color:#6c757d;
|
background-color:#6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scriptmessage {
|
|
||||||
max-width: 280px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scriptmessage.longer {
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
.mobile-bottomcol {
|
.mobile-bottomcol {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
.details-table td:first-child {
|
||||||
.scriptmessage {
|
white-space: pre-wrap;
|
||||||
max-width: 90px !important;
|
}
|
||||||
}
|
}
|
||||||
.scriptmessage.longer {
|
|
||||||
max-width: 280px !important;
|
|
||||||
|
.details-table {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-table td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-table td:nth-child(2) {
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
font-family: "Courier New", Courier, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smaller-text {
|
||||||
|
font-size: 12px;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.longer {
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 200px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row{
|
||||||
|
flex-direction: column;
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-info {
|
||||||
|
display: inline-table;
|
||||||
|
.fiat {
|
||||||
|
font-size: 14px;
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-fee {
|
||||||
|
display: block;
|
||||||
|
margin: 0px auto 5px;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
display: inline-table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.fiat {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
text-align: right;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
display: inline-table;
|
||||||
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,3 +80,182 @@ interface SpentInfo {
|
|||||||
inputIndex: number;
|
inputIndex: number;
|
||||||
txId: string;
|
txId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface BisqTrade {
|
||||||
|
direction: string;
|
||||||
|
price: string;
|
||||||
|
amount: string;
|
||||||
|
volume: string;
|
||||||
|
payment_method: string;
|
||||||
|
trade_id: string;
|
||||||
|
trade_date: number;
|
||||||
|
market?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Currencies { [txid: string]: Currency; }
|
||||||
|
|
||||||
|
export interface Currency {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
precision: number;
|
||||||
|
|
||||||
|
_type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Depth { [market: string]: Market; }
|
||||||
|
|
||||||
|
interface Market {
|
||||||
|
'buys': string[];
|
||||||
|
'sells': string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HighLowOpenClose {
|
||||||
|
period_start: number | string;
|
||||||
|
open: string;
|
||||||
|
high: string;
|
||||||
|
low: string;
|
||||||
|
close: string;
|
||||||
|
volume_left: string;
|
||||||
|
volume_right: string;
|
||||||
|
avg: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Markets { [txid: string]: Pair; }
|
||||||
|
|
||||||
|
interface Pair {
|
||||||
|
pair: string;
|
||||||
|
lname: string;
|
||||||
|
rname: string;
|
||||||
|
lsymbol: string;
|
||||||
|
rsymbol: string;
|
||||||
|
lprecision: number;
|
||||||
|
rprecision: number;
|
||||||
|
ltype: string;
|
||||||
|
rtype: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Offers { [market: string]: OffersMarket; }
|
||||||
|
|
||||||
|
export interface OffersMarket {
|
||||||
|
buys: Offer[] | null;
|
||||||
|
sells: Offer[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OffersData {
|
||||||
|
direction: string;
|
||||||
|
currencyCode: string;
|
||||||
|
minAmount: number;
|
||||||
|
amount: number;
|
||||||
|
price: number;
|
||||||
|
date: number;
|
||||||
|
useMarketBasedPrice: boolean;
|
||||||
|
marketPriceMargin: number;
|
||||||
|
paymentMethod: string;
|
||||||
|
id: string;
|
||||||
|
currencyPair: string;
|
||||||
|
primaryMarketDirection: string;
|
||||||
|
priceDisplayString: string;
|
||||||
|
primaryMarketAmountDisplayString: string;
|
||||||
|
primaryMarketMinAmountDisplayString: string;
|
||||||
|
primaryMarketVolumeDisplayString: string;
|
||||||
|
primaryMarketMinVolumeDisplayString: string;
|
||||||
|
primaryMarketPrice: number;
|
||||||
|
primaryMarketAmount: number;
|
||||||
|
primaryMarketMinAmount: number;
|
||||||
|
primaryMarketVolume: number;
|
||||||
|
primaryMarketMinVolume: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Offer {
|
||||||
|
offer_id: string;
|
||||||
|
offer_date: number;
|
||||||
|
direction: string;
|
||||||
|
min_amount: string;
|
||||||
|
amount: string;
|
||||||
|
price: string;
|
||||||
|
volume: string;
|
||||||
|
payment_method: string;
|
||||||
|
offer_fee_txid: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tickers { [market: string]: Ticker | null; }
|
||||||
|
|
||||||
|
export interface Ticker {
|
||||||
|
last: string;
|
||||||
|
high: string;
|
||||||
|
low: string;
|
||||||
|
volume_left: string;
|
||||||
|
volume_right: string;
|
||||||
|
buy: string | null;
|
||||||
|
sell: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Trade {
|
||||||
|
market?: string;
|
||||||
|
price: string;
|
||||||
|
amount: string;
|
||||||
|
volume: string;
|
||||||
|
payment_method: string;
|
||||||
|
trade_id: string;
|
||||||
|
trade_date: number;
|
||||||
|
_market: Pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TradesData {
|
||||||
|
currency: string;
|
||||||
|
direction: string;
|
||||||
|
tradePrice: number;
|
||||||
|
tradeAmount: number;
|
||||||
|
tradeDate: number;
|
||||||
|
paymentMethod: string;
|
||||||
|
offerDate: number;
|
||||||
|
useMarketBasedPrice: boolean;
|
||||||
|
marketPriceMargin: number;
|
||||||
|
offerAmount: number;
|
||||||
|
offerMinAmount: number;
|
||||||
|
offerId: string;
|
||||||
|
depositTxId?: string;
|
||||||
|
currencyPair: string;
|
||||||
|
primaryMarketDirection: string;
|
||||||
|
primaryMarketTradePrice: number;
|
||||||
|
primaryMarketTradeAmount: number;
|
||||||
|
primaryMarketTradeVolume: number;
|
||||||
|
|
||||||
|
_market: string;
|
||||||
|
_tradePriceStr: string;
|
||||||
|
_tradeAmountStr: string;
|
||||||
|
_tradeVolumeStr: string;
|
||||||
|
_offerAmountStr: string;
|
||||||
|
_tradePrice: number;
|
||||||
|
_tradeAmount: number;
|
||||||
|
_tradeVolume: number;
|
||||||
|
_offerAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarketVolume {
|
||||||
|
period_start: number;
|
||||||
|
num_trades: number;
|
||||||
|
volume: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarketsApiError {
|
||||||
|
success: number;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Interval = 'minute' | 'half_hour' | 'hour' | 'half_day' | 'day' | 'week' | 'month' | 'year' | 'auto';
|
||||||
|
|
||||||
|
export interface SummarizedIntervals { [market: string]: SummarizedInterval; }
|
||||||
|
export interface SummarizedInterval {
|
||||||
|
period_start: number;
|
||||||
|
open: number;
|
||||||
|
close: number;
|
||||||
|
high: number;
|
||||||
|
low: number;
|
||||||
|
avg: number;
|
||||||
|
volume_right: number;
|
||||||
|
volume_left: number;
|
||||||
|
time?: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ import { BisqRoutingModule } from './bisq.routing.module';
|
|||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
|
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
|
||||||
|
|
||||||
|
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
|
||||||
|
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
|
||||||
|
import { BisqMarketComponent } from './bisq-market/bisq-market.component';
|
||||||
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
||||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component';
|
import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component';
|
||||||
import { BisqBlockComponent } from './bisq-block/bisq-block.component';
|
import { BisqBlockComponent } from './bisq-block/bisq-block.component';
|
||||||
|
import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.component';
|
||||||
|
import { BisqMainDashboardComponent } from './bisq-main-dashboard/bisq-main-dashboard.component';
|
||||||
import { BisqIconComponent } from './bisq-icon/bisq-icon.component';
|
import { BisqIconComponent } from './bisq-icon/bisq-icon.component';
|
||||||
import { BisqTransactionDetailsComponent } from './bisq-transaction-details/bisq-transaction-details.component';
|
import { BisqTransactionDetailsComponent } from './bisq-transaction-details/bisq-transaction-details.component';
|
||||||
import { BisqTransfersComponent } from './bisq-transfers/bisq-transfers.component';
|
import { BisqTransfersComponent } from './bisq-transfers/bisq-transfers.component';
|
||||||
@@ -14,11 +19,11 @@ import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontaweso
|
|||||||
import { faLeaf, faQuestion, faExclamationTriangle, faRocket, faRetweet, faFileAlt, faMoneyBill,
|
import { faLeaf, faQuestion, faExclamationTriangle, faRocket, faRetweet, faFileAlt, faMoneyBill,
|
||||||
faEye, faEyeSlash, faLock, faLockOpen, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
|
faEye, faEyeSlash, faLock, faLockOpen, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
||||||
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
|
|
||||||
import { BisqApiService } from './bisq-api.service';
|
import { BisqApiService } from './bisq-api.service';
|
||||||
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
||||||
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
||||||
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
||||||
|
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -30,10 +35,15 @@ import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
|||||||
BisqTransactionDetailsComponent,
|
BisqTransactionDetailsComponent,
|
||||||
BisqTransfersComponent,
|
BisqTransfersComponent,
|
||||||
BisqBlocksComponent,
|
BisqBlocksComponent,
|
||||||
BisqExplorerComponent,
|
|
||||||
BisqAddressComponent,
|
BisqAddressComponent,
|
||||||
BisqStatsComponent,
|
BisqStatsComponent,
|
||||||
BsqAmountComponent,
|
BsqAmountComponent,
|
||||||
|
LightweightChartsComponent,
|
||||||
|
LightweightChartsAreaComponent,
|
||||||
|
BisqDashboardComponent,
|
||||||
|
BisqMarketComponent,
|
||||||
|
BisqTradesComponent,
|
||||||
|
BisqMainDashboardComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BisqRoutingModule,
|
BisqRoutingModule,
|
||||||
|
|||||||
@@ -5,55 +5,68 @@ import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions
|
|||||||
import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component';
|
import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component';
|
||||||
import { BisqBlockComponent } from './bisq-block/bisq-block.component';
|
import { BisqBlockComponent } from './bisq-block/bisq-block.component';
|
||||||
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
||||||
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
|
|
||||||
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
||||||
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
||||||
import { ApiDocsComponent } from '../components/api-docs/api-docs.component';
|
import { ApiDocsComponent } from '../components/api-docs/api-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';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: BisqExplorerComponent,
|
component: BisqMainDashboardComponent,
|
||||||
children: [
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: 'markets',
|
||||||
component: BisqTransactionsComponent
|
component: BisqDashboardComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tx/:id',
|
path: 'transactions',
|
||||||
component: BisqTransactionComponent
|
component: BisqTransactionsComponent
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'blocks',
|
path: 'market/:pair',
|
||||||
children: [],
|
component: BisqMarketComponent,
|
||||||
component: BisqBlocksComponent
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tx/:id',
|
||||||
path: 'block/:id',
|
component: BisqTransactionComponent
|
||||||
component: BisqBlockComponent,
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'blocks',
|
||||||
path: 'address/:id',
|
children: [],
|
||||||
component: BisqAddressComponent,
|
component: BisqBlocksComponent
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'stats',
|
path: 'block/:id',
|
||||||
component: BisqStatsComponent,
|
component: BisqBlockComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'address/:id',
|
||||||
component: AboutComponent,
|
component: BisqAddressComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'api',
|
path: 'stats',
|
||||||
component: ApiDocsComponent,
|
component: BisqStatsComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: 'about',
|
||||||
redirectTo: ''
|
component: AboutComponent,
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
}
|
path: 'api',
|
||||||
|
component: ApiDocsComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'terms-of-service',
|
||||||
|
component: TermsOfServiceComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
redirectTo: ''
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
<span [class.green-color]="green">{{ conversions.USD * bsq / 100 * (bsqPrice$ | async) / 100000000 | currency:'USD':'symbol':'1.2-2' }}</span>
|
<span [class.green-color]="green">{{ conversions.USD * bsq / 100 * (bsqPrice$ | async) / 100000000 | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #viewFiatVin>
|
<ng-template #viewFiatVin>
|
||||||
{{ bsq / 100 | number : digitsInfo }} BSQ
|
{{ bsq / 100 | number : digitsInfo }} <span class="symbol">BSQ</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
:host ::ng-deep .floating-tooltip-2 {
|
||||||
|
width: 160px;
|
||||||
|
height: 80px;
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 12px;
|
||||||
|
color:rgba(255, 255, 255, 1);
|
||||||
|
background-color: #131722;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 1000;
|
||||||
|
top: 12px;
|
||||||
|
left: 12px;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep .volumeText {
|
||||||
|
color: rgba(33, 150, 243, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep .tradesText {
|
||||||
|
color: rgba(37, 177, 53, 1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { createChart, CrosshairMode, isBusinessDay } from 'lightweight-charts';
|
||||||
|
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-lightweight-charts-area',
|
||||||
|
template: '<ng-component></ng-component>',
|
||||||
|
styleUrls: ['./lightweight-charts-area.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class LightweightChartsAreaComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
@Input() data: any;
|
||||||
|
@Input() lineData: any;
|
||||||
|
@Input() precision: number;
|
||||||
|
@Input() height = 500;
|
||||||
|
|
||||||
|
areaSeries: any;
|
||||||
|
volumeSeries: any;
|
||||||
|
chart: any;
|
||||||
|
lineSeries: any;
|
||||||
|
container: any;
|
||||||
|
|
||||||
|
width: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private element: ElementRef,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.width = this.element.nativeElement.parentElement.offsetWidth;
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
const chartholder = this.element.nativeElement.appendChild(this.container);
|
||||||
|
|
||||||
|
this.chart = createChart(chartholder, {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
crosshair: {
|
||||||
|
mode: CrosshairMode.Normal,
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
backgroundColor: '#000',
|
||||||
|
textColor: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
vertLines: {
|
||||||
|
color: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
},
|
||||||
|
horzLines: {
|
||||||
|
color: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rightPriceScale: {
|
||||||
|
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
},
|
||||||
|
timeScale: {
|
||||||
|
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lineSeries = this.chart.addLineSeries({
|
||||||
|
color: 'rgba(37, 177, 53, 1)',
|
||||||
|
lineColor: 'rgba(216, 27, 96, 1)',
|
||||||
|
lineWidth: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.areaSeries = this.chart.addAreaSeries({
|
||||||
|
topColor: 'rgba(33, 150, 243, 0.7)',
|
||||||
|
bottomColor: 'rgba(33, 150, 243, 0.1)',
|
||||||
|
lineColor: 'rgba(33, 150, 243, 0.1)',
|
||||||
|
lineWidth: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toolTip = document.createElement('div');
|
||||||
|
toolTip.className = 'floating-tooltip-2';
|
||||||
|
chartholder.appendChild(toolTip);
|
||||||
|
|
||||||
|
this.chart.subscribeCrosshairMove((param) => {
|
||||||
|
if (!param.time || param.point.x < 0 || param.point.x > this.width || param.point.y < 0 || param.point.y > this.height) {
|
||||||
|
toolTip.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateStr = isBusinessDay(param.time)
|
||||||
|
? this.businessDayToString(param.time)
|
||||||
|
: new Date(param.time * 1000).toLocaleDateString();
|
||||||
|
|
||||||
|
toolTip.style.display = 'block';
|
||||||
|
const price = param.seriesPrices.get(this.areaSeries);
|
||||||
|
const line = param.seriesPrices.get(this.lineSeries);
|
||||||
|
|
||||||
|
const tradesText = $localize`:@@bisq-graph-trades:Trades`;
|
||||||
|
const volumeText = $localize`:@@bisq-graph-volume:Volume`;
|
||||||
|
|
||||||
|
toolTip.innerHTML = `<table>
|
||||||
|
<tr><td class="tradesText">${tradesText}:</td><td class="text-right tradesText">${Math.round(line * 100) / 100}</td></tr>
|
||||||
|
<tr><td class="volumeText">${volumeText}:<td class="text-right volumeText">${Math.round(price * 100) / 100} BTC</td></tr>
|
||||||
|
</table>
|
||||||
|
<div>${dateStr}</div>`;
|
||||||
|
|
||||||
|
const y = param.point.y;
|
||||||
|
|
||||||
|
const toolTipWidth = 100;
|
||||||
|
const toolTipHeight = 80;
|
||||||
|
const toolTipMargin = 15;
|
||||||
|
|
||||||
|
let left = param.point.x + toolTipMargin;
|
||||||
|
if (left > this.width - toolTipWidth) {
|
||||||
|
left = param.point.x - toolTipMargin - toolTipWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
let top = y + toolTipMargin;
|
||||||
|
if (top > this.height - toolTipHeight) {
|
||||||
|
top = y - toolTipHeight - toolTipMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
toolTip.style.left = left + 'px';
|
||||||
|
toolTip.style.top = top + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
businessDayToString(businessDay) {
|
||||||
|
return businessDay.year + '-' + businessDay.month + '-' + businessDay.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (!changes.data || changes.data.isFirstChange()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData() {
|
||||||
|
this.areaSeries.setData(this.data);
|
||||||
|
this.lineSeries.setData(this.lineData);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.chart.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { createChart, CrosshairMode } from 'lightweight-charts';
|
||||||
|
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-lightweight-charts',
|
||||||
|
template: '<ng-component></ng-component>',
|
||||||
|
styleUrls: ['./lightweight-charts.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class LightweightChartsComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
@Input() data: any;
|
||||||
|
@Input() volumeData: any;
|
||||||
|
@Input() precision: number;
|
||||||
|
@Input() height = 500;
|
||||||
|
|
||||||
|
lineSeries: any;
|
||||||
|
volumeSeries: any;
|
||||||
|
chart: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private element: ElementRef,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.chart = createChart(this.element.nativeElement, {
|
||||||
|
width: this.element.nativeElement.parentElement.offsetWidth,
|
||||||
|
height: this.height,
|
||||||
|
layout: {
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
textColor: '#d1d4dc',
|
||||||
|
},
|
||||||
|
crosshair: {
|
||||||
|
mode: CrosshairMode.Normal,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
vertLines: {
|
||||||
|
visible: true,
|
||||||
|
color: 'rgba(42, 46, 57, 0.5)',
|
||||||
|
},
|
||||||
|
horzLines: {
|
||||||
|
color: 'rgba(42, 46, 57, 0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.lineSeries = this.chart.addCandlestickSeries();
|
||||||
|
|
||||||
|
this.volumeSeries = this.chart.addHistogramSeries({
|
||||||
|
color: '#26a69a',
|
||||||
|
priceFormat: {
|
||||||
|
type: 'volume',
|
||||||
|
},
|
||||||
|
priceScaleId: '',
|
||||||
|
scaleMargins: {
|
||||||
|
top: 0.85,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (!changes.data || changes.data.isFirstChange()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.chart.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData() {
|
||||||
|
this.lineSeries.setData(this.data);
|
||||||
|
this.volumeSeries.setData(this.volumeData);
|
||||||
|
|
||||||
|
this.lineSeries.applyOptions({
|
||||||
|
priceFormat: {
|
||||||
|
type: 'price',
|
||||||
|
precision: this.precision,
|
||||||
|
minMove: 0.0000001,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,408 +1,214 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl about-page">
|
||||||
<div class="text-center">
|
|
||||||
<br>
|
|
||||||
<img src="./resources/mempool-logo-bigger.png" height="62.5" width="250">
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div class="text-small text-center offset-md-1">
|
<div class="intro">
|
||||||
|
<span style="margin-left: auto; margin-right: -20px; margin-bottom: -20px">™</span>
|
||||||
|
<img class="logo" src="./resources/mempool-logo-bigger.png" />
|
||||||
|
<div class="version">
|
||||||
v{{ packetJsonVersion }} [{{ frontendGitCommitHash }}]
|
v{{ packetJsonVersion }} [{{ frontendGitCommitHash }}]
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<h5 i18n="about.about-the-project">The Mempool Open Source Project</h5>
|
|
||||||
<div class="row row-cols-1">
|
|
||||||
<div class="col col-md-6 mx-auto">
|
|
||||||
<p i18n>An explorer and API developed and operated for the Bitcoin community, focusing on the emerging transaction fee market to help our transition into a multi-layer ecosystem, without ads, altcoins, or third-party trackers.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://github.com/mempool/mempool">
|
|
||||||
<span class="dib v-mid">
|
|
||||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://twitter.com/mempool">
|
|
||||||
<span class="dib v-mid">
|
|
||||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://t.me/mempoolspace">
|
|
||||||
<span class="dib v-mid">
|
|
||||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M446.7 98.6l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1L117.8 284 16.2 252.2c-22.1-6.9-22.5-22.1 4.6-32.7L418.2 66.4c18.4-6.9 34.5 4.1 28.5 32.2z"></path></svg>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://keybase.io/team/mempool">
|
|
||||||
<span class="dib v-mid">
|
|
||||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M286.17 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18zm111.92-147.6c-9.5-14.62-39.37-52.45-87.26-73.71q-9.1-4.06-18.38-7.27a78.43 78.43 0 0 0-47.88-104.13c-12.41-4.1-23.33-6-32.41-5.77-.6-2-1.89-11 9.4-35L198.66 32l-5.48 7.56c-8.69 12.06-16.92 23.55-24.34 34.89a51 51 0 0 0-8.29-1.25c-41.53-2.45-39-2.33-41.06-2.33-50.61 0-50.75 52.12-50.75 45.88l-2.36 36.68c-1.61 27 19.75 50.21 47.63 51.85l8.93.54a214 214 0 0 0-46.29 35.54C14 304.66 14 374 14 429.77v33.64l23.32-29.8a148.6 148.6 0 0 0 14.56 37.56c5.78 10.13 14.87 9.45 19.64 7.33 4.21-1.87 10-6.92 3.75-20.11a178.29 178.29 0 0 1-15.76-53.13l46.82-59.83-24.66 74.11c58.23-42.4 157.38-61.76 236.25-38.59 34.2 10.05 67.45.69 84.74-23.84.72-1 1.2-2.16 1.85-3.22a156.09 156.09 0 0 1 2.8 28.43c0 23.3-3.69 52.93-14.88 81.64-2.52 6.46 1.76 14.5 8.6 15.74 7.42 1.57 15.33-3.1 18.37-11.15C429 443 434 414 434 382.32c0-38.58-13-77.46-35.91-110.92zM142.37 128.58l-15.7-.93-1.39 21.79 13.13.78a93 93 0 0 0 .32 19.57l-22.38-1.34a12.28 12.28 0 0 1-11.76-12.79L107 119c1-12.17 13.87-11.27 13.26-11.32l29.11 1.73a144.35 144.35 0 0 0-7 19.17zm148.42 172.18a10.51 10.51 0 0 1-14.35-1.39l-9.68-11.49-34.42 27a8.09 8.09 0 0 1-11.13-1.08l-15.78-18.64a7.38 7.38 0 0 1 1.34-10.34l34.57-27.18-14.14-16.74-17.09 13.45a7.75 7.75 0 0 1-10.59-1s-3.72-4.42-3.8-4.53a7.38 7.38 0 0 1 1.37-10.34L214 225.19s-18.51-22-18.6-22.14a9.56 9.56 0 0 1 1.74-13.42 10.38 10.38 0 0 1 14.3 1.37l81.09 96.32a9.58 9.58 0 0 1-1.74 13.44zM187.44 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18z"></path></svg>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<h3 i18n="about.sponsors.enterprise">Enterprise Sponsors 🚀</h3>
|
|
||||||
|
|
||||||
<a href="https://squarecrypto.org/" target="_blank">
|
|
||||||
<div class="profile_photo enterprise_sponsor d-inline-block" title="Square Crypto">
|
|
||||||
<img class="profile_img" src="/resources/profile/sqcrypto.svg" />
|
|
||||||
Square
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://gemini.com/" target="_blank">
|
|
||||||
<div class="profile_photo enterprise_sponsor d-inline-block" title="Gemini">
|
|
||||||
<img class="profile_img" src="/resources/profile/gemini.svg" />
|
|
||||||
Gemini
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://exodus.com/" target="_blank">
|
|
||||||
<div class="profile_photo enterprise_sponsor d-inline-block" title="Exodus">
|
|
||||||
<img class="profile_img" src="/resources/profile/exodus.svg" />
|
|
||||||
Exodus
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<h3 i18n="about.sponsors.withHeart">Community Sponsors ❤️</h3>
|
|
||||||
|
|
||||||
<div *ngIf="sponsors === null">
|
|
||||||
<br>
|
|
||||||
<div class="spinner-border text-light"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template ngFor let-sponsor [ngForOf]="sponsors">
|
|
||||||
<a [href]="'https://twitter.com/' + sponsor.handle" target="_blank" rel="sponsored">
|
|
||||||
<div class="profile_photo community_sponsor d-inline-block" [title]="sponsor.handle">
|
|
||||||
<img class="profile_img" [src]="'/api/v1/donations/images/' + sponsor.handle" />
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</ng-template>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-primary" (click)="donationStatus = 2" [hidden]="donationStatus !== 1" i18n="about.become-a-sponsor">Become a sponsor ❤️</button>
|
|
||||||
<p *ngIf="donationStatus === 2 && !sponsorsEnabled">
|
|
||||||
<ng-container i18n="about.navigate-to-sponsor">Navigate to <a href="https://mempool.space/about" target="_blank">https://mempool.space/about</a> to sponsor</ng-container>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div style="max-width: 300px;" class="mx-auto" [hidden]="donationStatus !== 2 || !sponsorsEnabled">
|
|
||||||
<form [formGroup]="donationForm" (submit)="submitDonation()" class="form">
|
|
||||||
<div class="input-group mb-2">
|
|
||||||
<div class="input-group-prepend" style="width: 42px;">
|
|
||||||
<span class="input-group-text">₿</span>
|
|
||||||
</div>
|
|
||||||
<input formControlName="amount" class="form-control" type="number" min="0.001" step="1E-03">
|
|
||||||
</div>
|
|
||||||
<div class="input-group" *ngIf="donationForm.get('amount').value >= 0.01; else lowAmount">
|
|
||||||
<div class="input-group-prepend" style="width: 42px;">
|
|
||||||
<span class="input-group-text">@</span>
|
|
||||||
</div>
|
|
||||||
<input formControlName="handle" class="form-control" type="text" placeholder="Twitter handle (Optional)">
|
|
||||||
</div>
|
|
||||||
<div class="required" *ngIf="donationForm.get('amount').hasError('required')" i18n="about.sponsor.amount-required">Amount required</div>
|
|
||||||
<div class="required" *ngIf="donationForm.get('amount').hasError('min')" i18n="about.sponsor.minimum-amount">Minimum amount is 0.001 BTC</div>
|
|
||||||
<div class="input-group mt-4">
|
|
||||||
<button class="btn btn-primary mx-auto" type="submit" [disabled]="donationForm.invalid" i18n="about.sponsor.request-invoice">Request invoice</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template #lowAmount>
|
|
||||||
<div class="input-group mb-4 text-small">
|
|
||||||
<span i18n="about.sponsor.description">If you donate 0.01 BTC or more, your profile photo will be added to the list of sponsors above :)</span>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div *ngIf="donationStatus === 3" class="text-center">
|
|
||||||
|
|
||||||
<form [formGroup]="paymentForm">
|
|
||||||
<div class="btn-group btn-group-toggle mb-2" ngbRadioGroup formControlName="method">
|
|
||||||
<label ngbButtonLabel class="btn-primary">
|
|
||||||
<input ngbButton type="radio" value="chain"> <fa-icon [icon]="['fas', 'link']" [fixedWidth]="true" title="Onchain"></fa-icon>
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary" *ngIf="donationObj.addresses.BTC_LightningLike">
|
|
||||||
<input ngbButton type="radio" value="lightning"> <fa-icon [icon]="['fas', 'bolt']" [fixedWidth]="true" title="Lightning"></fa-icon>
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary" *ngIf="donationObj.addresses.LBTC">
|
|
||||||
<input ngbButton type="radio" value="lbtc"> <fa-icon [icon]="['fas', 'tint']" [fixedWidth]="true" title="Liquid Bitcoin"></fa-icon>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="paymentForm.get('method').value === 'chain'">
|
|
||||||
<div class="qr-wrapper mt-2 mb-2">
|
|
||||||
<a [href]="bypassSecurityTrustUrl('bitcoin:' + donationObj.addresses.BTC + '?amount=' + donationObj.amount)" target="_blank">
|
|
||||||
<app-qrcode imageUrl="./resources/bitcoin-logo.png" [size]="200" [data]="'bitcoin:' + donationObj.addresses.BTC + '?amount=' + donationObj.amount"></app-qrcode>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="input-group input-group-sm mb-3 mt-3 info-group mx-auto">
|
|
||||||
<input type="text" class="form-control" readonly [value]="donationObj.addresses.BTC">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" ><app-clipboard [text]="donationObj.addresses.BTC"></app-clipboard></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p style="font-size: 12px;">{{ donationObj.amount }} BTC</p>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="paymentForm.get('method').value === 'lightning'">
|
|
||||||
<div class="qr-wrapper mt-2 mb-2">
|
|
||||||
<a [href]="bypassSecurityTrustUrl('lightning:' + donationObj.addresses.BTC_LightningLike)" target="_blank">
|
|
||||||
<app-qrcode imageUrl="./resources/bitcoin-logo.png" [size]="200" [data]="donationObj.addresses.BTC_LightningLike.toUpperCase()"></app-qrcode>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div class="input-group input-group-sm mb-3 mt-3 info-group mx-auto">
|
|
||||||
<input type="text" class="form-control" readonly [value]="donationObj.addresses.BTC_LightningLike">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-outline-secondary" type="button"><app-clipboard [text]="donationObj.addresses.BTC_LightningLike"></app-clipboard></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="input-group input-group-sm mb-3 mt-3 info-group mx-auto">
|
|
||||||
<input type="text" class="form-control" readonly value="036f7fad4938521ddc6fc87ab7d6c6a091cef23cad87564a1f55adb806c017575e@103.99.170.198:9735">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-outline-secondary" type="button"><app-clipboard [text]="'036f7fad4938521ddc6fc87ab7d6c6a091cef23cad87564a1f55adb806c017575e@103.99.170.198:9735'"></app-clipboard></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p style="font-size: 10px;"></p>
|
|
||||||
<p style="font-size: 12px;">{{ donationObj.amount }} BTC</p>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template [ngIf]="paymentForm.get('method').value === 'lbtc'">
|
|
||||||
<div class="qr-wrapper mt-2 mb-2">
|
|
||||||
<a [href]="bypassSecurityTrustUrl('liquidnetwork:' + donationObj.addresses.LBTC + '?amount=' + donationObj.amount + '&assetid=6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d')" target="_blank">
|
|
||||||
<app-qrcode imageUrl="./resources/liquid-bitcoin.png" [size]="200" [data]="'liquidnetwork:' + donationObj.addresses.LBTC + '?amount=' + donationObj.amount + '&assetid=6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d'"></app-qrcode>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="input-group input-group-sm mb-3 mt-3 info-group mx-auto">
|
|
||||||
<input type="text" class="form-control" readonly [value]="donationObj.addresses.LBTC">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" ><app-clipboard [text]="donationObj.addresses.LBTC"></app-clipboard></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p style="font-size: 12px;">{{ donationObj.amount }} BTC</p>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<p i18n="about.sponsor.waiting-for-transaction">Waiting for transaction... </p>
|
|
||||||
<div class="spinner-border text-light"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="donationStatus === 4" class="text-center">
|
|
||||||
<h2><span i18n="about.sponsor.donation-confirmed">Donation confirmed!</span><br><span i18n="about.sponsor.thank-you">Thank you!</span></h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<h3 i18n="about.integrations">Community Integrations</h3>
|
|
||||||
|
|
||||||
<a href="https://github.com/bisq-network/bisq" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Bisq">
|
|
||||||
<img class="profile_img" src="/resources/profile/bisq_network.png" />
|
|
||||||
Bisq
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/getumbrel/umbrel" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Umbrel">
|
|
||||||
<img class="profile_img" src="/resources/profile/umbrel.png" />
|
|
||||||
Umbrel
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/rootzoll/raspiblitz" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="RaspiBlitz">
|
|
||||||
<img class="profile_img" src="/resources/profile/raspiblitz.jpg" />
|
|
||||||
RaspiBlitz
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/mynodebtc/mynode" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="MyNode">
|
|
||||||
<img class="profile_img" src="/resources/profile/mynodebtc.jpg" />
|
|
||||||
MyNode
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/RoninDojo/RoninDojo" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="RoninDojo">
|
|
||||||
<img class="profile_img" src="/resources/profile/ronindojo.png" />
|
|
||||||
RoninDojo
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/spesmilo/electrum" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Electrum Wallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/electrum.jpg" />
|
|
||||||
Electrum
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/cryptoadvance/specter-desktop" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Specter Wallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/specter.png" />
|
|
||||||
Specter
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/sparrowwallet/sparrow" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Sparrow Wallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/sparrow.png" />
|
|
||||||
Sparrow
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/ACINQ/phoenix" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Phoenix Wallet by ACINQ">
|
|
||||||
<img class="profile_img" src="/resources/profile/phoenix.jpg" />
|
|
||||||
Phoenix
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/muun/apollo" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Muun Wallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/muun.png" />
|
|
||||||
Muun
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/BlueWallet/BlueWallet" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="BlueWallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/bluewallet.png" />
|
|
||||||
BlueWallet
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/hsjoberg/blixt-wallet" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Blixt Wallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/blixt.png" />
|
|
||||||
Blixt
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/Satpile/satpile" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Satpile Watch-Only Wallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/satpile.jpg" />
|
|
||||||
Satpile
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/btcontract/lnwallet" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="Bitcoin Lightning Wallet">
|
|
||||||
<img class="profile_img" src="/resources/profile/blw.png" />
|
|
||||||
BLW
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://github.com/pxsocs/warden" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="WARden Portfolio">
|
|
||||||
<img class="profile_img" src="/resources/profile/warden.jpg" />
|
|
||||||
WARden
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<h3 i18n="about.alliances">Community Alliances</h3>
|
|
||||||
|
|
||||||
<a href="https://liquid.net/">
|
|
||||||
<img src="/resources/profile/liquid.svg" height="52">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://opencrypto.org/">
|
|
||||||
<img src="/resources/profile/copa.png" height="62" style="margin-left: 30px; margin-right: 30px; margin-top: 20px;">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://bisq.network/">
|
|
||||||
<img src="/resources/profile/bisq.svg" height="62">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<h3 i18n="about.contributors">Project Contributors</h3>
|
|
||||||
|
|
||||||
<div *ngIf="contributors === null">
|
|
||||||
<br>
|
|
||||||
<div class="spinner-border text-light"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template ngFor let-contributor [ngForOf]="contributors">
|
|
||||||
<a [href]="'https://github.com/' + contributor.name" target="_blank">
|
|
||||||
<div class="profile_photo project_contributor d-inline-block" [title]="contributor.name">
|
|
||||||
<img class="profile_img" [src]="'/api/v1/contributors/images/' + contributor.id" />
|
|
||||||
{{ contributor.name }}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<h3 i18n="about.maintainers">Project Maintainers</h3>
|
|
||||||
|
|
||||||
<a href="https://twitter.com/softsimon_" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="softsimon">
|
|
||||||
<img class="profile_img" src="/resources/profile/softsimon.jpg" />
|
|
||||||
softsimon
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://twitter.com/wiz" target="_blank">
|
|
||||||
<div class="profile_photo d-inline-block" title="wiz">
|
|
||||||
<img class="profile_img" src="/resources/profile/wiz.png" />
|
|
||||||
wiz
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<div class="col-md-8 offset-md-2 text-center">
|
|
||||||
<code>Copyright (c) 2019-2021</code><br>
|
|
||||||
<code>The Mempool Open Source Project</code><br>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="col-md-8 offset-md-2 text-left" style="padding-left: 20px">
|
|
||||||
<code>This program is free software; you can redistribute it and/or modify it under the terms of (at your option) either:</code><br>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="col-md-8 offset-md-2 text-left" style="padding-left: 40px">
|
|
||||||
<code> 1) the <a href="https://www.gnu.org/licenses/agpl-3.0-standalone.html">GNU Affero General Public License</a> as published by the Free Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on <https://mempool.space/about>; or</code><br>
|
|
||||||
<br>
|
|
||||||
<code> 2) the <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU General Public License</a> as published by the Free Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on <https://mempool.space/about>.</code><br>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div class="col-md-8 offset-md-2 text-left" style="padding-left: 20px">
|
|
||||||
<code>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the full license terms for more details.</code><br>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<div class="about-text">
|
||||||
|
<h5><ng-container i18n="about.about-the-project">The Mempool Open Source Project</ng-container> ™</h5>
|
||||||
|
<p i18n>Building a mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, without any advertising, altcoins, or third-party trackers.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="social-icons">
|
||||||
|
<a target="_blank" href="https://github.com/mempool/mempool">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" href="https://twitter.com/mempool">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" href="https://t.me/mempoolspace">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M446.7 98.6l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1L117.8 284 16.2 252.2c-22.1-6.9-22.5-22.1 4.6-32.7L418.2 66.4c18.4-6.9 34.5 4.1 28.5 32.2z"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" href="https://keybase.io/team/mempool">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M286.17 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18zm111.92-147.6c-9.5-14.62-39.37-52.45-87.26-73.71q-9.1-4.06-18.38-7.27a78.43 78.43 0 0 0-47.88-104.13c-12.41-4.1-23.33-6-32.41-5.77-.6-2-1.89-11 9.4-35L198.66 32l-5.48 7.56c-8.69 12.06-16.92 23.55-24.34 34.89a51 51 0 0 0-8.29-1.25c-41.53-2.45-39-2.33-41.06-2.33-50.61 0-50.75 52.12-50.75 45.88l-2.36 36.68c-1.61 27 19.75 50.21 47.63 51.85l8.93.54a214 214 0 0 0-46.29 35.54C14 304.66 14 374 14 429.77v33.64l23.32-29.8a148.6 148.6 0 0 0 14.56 37.56c5.78 10.13 14.87 9.45 19.64 7.33 4.21-1.87 10-6.92 3.75-20.11a178.29 178.29 0 0 1-15.76-53.13l46.82-59.83-24.66 74.11c58.23-42.4 157.38-61.76 236.25-38.59 34.2 10.05 67.45.69 84.74-23.84.72-1 1.2-2.16 1.85-3.22a156.09 156.09 0 0 1 2.8 28.43c0 23.3-3.69 52.93-14.88 81.64-2.52 6.46 1.76 14.5 8.6 15.74 7.42 1.57 15.33-3.1 18.37-11.15C429 443 434 414 434 382.32c0-38.58-13-77.46-35.91-110.92zM142.37 128.58l-15.7-.93-1.39 21.79 13.13.78a93 93 0 0 0 .32 19.57l-22.38-1.34a12.28 12.28 0 0 1-11.76-12.79L107 119c1-12.17 13.87-11.27 13.26-11.32l29.11 1.73a144.35 144.35 0 0 0-7 19.17zm148.42 172.18a10.51 10.51 0 0 1-14.35-1.39l-9.68-11.49-34.42 27a8.09 8.09 0 0 1-11.13-1.08l-15.78-18.64a7.38 7.38 0 0 1 1.34-10.34l34.57-27.18-14.14-16.74-17.09 13.45a7.75 7.75 0 0 1-10.59-1s-3.72-4.42-3.8-4.53a7.38 7.38 0 0 1 1.37-10.34L214 225.19s-18.51-22-18.6-22.14a9.56 9.56 0 0 1 1.74-13.42 10.38 10.38 0 0 1 14.3 1.37l81.09 96.32a9.58 9.58 0 0 1-1.74 13.44zM187.44 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18z"></path></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="enterprise-sponsor">
|
||||||
|
<h3 i18n="about.sponsors.enterprise.withRocket">Enterprise Sponsors 🚀</h3>
|
||||||
|
<div class="wrapper">
|
||||||
|
<a href="https://squarecrypto.org/" target="_blank" title="Square Crypto">
|
||||||
|
<img class="image" src="/resources/profile/sqcrypto.svg" />
|
||||||
|
<span>Square</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://gemini.com/" target="_blank" title="Gemini">
|
||||||
|
<img class="image" src="/resources/profile/gemini.svg" />
|
||||||
|
<span>Gemini</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://exodus.com/" target="_blank" title="Exodus">
|
||||||
|
<img class="image" src="/resources/profile/exodus.svg" />
|
||||||
|
<span>Exodus</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="community-sponsor">
|
||||||
|
<h3 i18n="about.sponsors.withHeart">Community Sponsors ❤️</h3>
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<ng-container *ngIf="sponsors$ | async as sponsors; else loadingSponsors">
|
||||||
|
<ng-template ngFor let-sponsor [ngForOf]="sponsors">
|
||||||
|
<a [href]="'https://twitter.com/' + sponsor.handle" target="_blank" rel="sponsored" [title]="sponsor.handle">
|
||||||
|
<img class="image" [src]="'/api/v1/donations/images/' + sponsor.handle" />
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button [hidden]="showNavigateToSponsor" type="button" class="btn btn-primary" (click)="sponsor()" i18n="about.become-a-sponsor">Become a sponsor ❤️</button>
|
||||||
|
<ng-container *ngIf="showNavigateToSponsor" i18n="about.navigate-to-sponsor">Navigate to <a href="https://mempool.space/sponsor" target="_blank">https://mempool.space/sponsor</a> to sponsor</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="community-integrations-sponsor">
|
||||||
|
<h3 i18n="about.integrations">Community Integrations</h3>
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<a href="https://github.com/bisq-network/bisq" target="_blank" title="Bisq">
|
||||||
|
<img class="image" src="/resources/profile/bisq_network.png" />
|
||||||
|
<span>Bisq</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
||||||
|
<img class="image" src="/resources/profile/umbrel.png" />
|
||||||
|
<span>Umbrel</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/rootzoll/raspiblitz" target="_blank" title="RaspiBlitz">
|
||||||
|
<img class="image" src="/resources/profile/raspiblitz.jpg" />
|
||||||
|
<span>RaspiBlitz</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/mynodebtc/mynode" target="_blank" title="MyNode">
|
||||||
|
<img class="image" src="/resources/profile/mynodebtc.jpg" />
|
||||||
|
<span>MyNode</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/RoninDojo/RoninDojo" target="_blank" title="RoninDojo">
|
||||||
|
<img class="image" src="/resources/profile/ronindojo.png" />
|
||||||
|
<span>RoninDojo</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
||||||
|
<img class="image" src="/resources/profile/electrum.jpg" />
|
||||||
|
<span>Electrum</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/cryptoadvance/specter-desktop" target="_blank" title="Specter Wallet">
|
||||||
|
<img class="image" src="/resources/profile/specter.png" />
|
||||||
|
<span>Specter</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/sparrowwallet/sparrow" target="_blank" title="Sparrow Wallet">
|
||||||
|
<img class="image" src="/resources/profile/sparrow.png" />
|
||||||
|
<span>Sparrow</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/ACINQ/phoenix" target="_blank" title="Phoenix Wallet by ACINQ">
|
||||||
|
<img class="image" src="/resources/profile/phoenix.jpg" />
|
||||||
|
<span>Phoenix</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/muun/apollo" target="_blank" title="Muun Wallet">
|
||||||
|
<img class="image" src="/resources/profile/muun.png" />
|
||||||
|
<span>Muun</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/BlueWallet/BlueWallet" target="_blank" title="BlueWallet">
|
||||||
|
<img class="image" src="/resources/profile/bluewallet.png" />
|
||||||
|
<span>BlueWallet</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/hsjoberg/blixt-wallet" target="_blank" title="Blixt Wallet">
|
||||||
|
<img class="image" src="/resources/profile/blixt.png" />
|
||||||
|
<span>Blixt</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/Satpile/satpile" target="_blank" title="Satpile Watch-Only Wallet">
|
||||||
|
<img class="image" src="/resources/profile/satpile.jpg" />
|
||||||
|
<span>Satpile</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/btcontract/lnwallet" target="_blank" title="Bitcoin Lightning Wallet">
|
||||||
|
<img class="image" src="/resources/profile/blw.png" />
|
||||||
|
<span>BLW</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/pxsocs/warden" target="_blank" title="WARden Portfolio">
|
||||||
|
<img class="image" src="/resources/profile/warden.jpg" />
|
||||||
|
<span>WARden</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alliances">
|
||||||
|
<h3 i18n="about.alliances">Community Alliances</h3>
|
||||||
|
<div class="wrapper">
|
||||||
|
<a href="https://liquid.net/" title="Liquid Network">
|
||||||
|
<img class="liquid" src="/resources/profile/liquid.svg" />
|
||||||
|
</a>
|
||||||
|
<a href="https://opencrypto.org/" title="Coppa - Crypto Open Patent Alliance">
|
||||||
|
<img class="copa" src="/resources/profile/copa.png" />
|
||||||
|
</a>
|
||||||
|
<a href="https://bisq.network/" title="Bisq Network">
|
||||||
|
<img class="bisq" src="/resources/profile/bisq.svg" />
|
||||||
|
</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">
|
||||||
|
<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 class="maintainers">
|
||||||
|
<h3 i18n="about.maintainers">Project Maintainers</h3>
|
||||||
|
<div class="wrapper">
|
||||||
|
<a href="https://twitter.com/softsimon_" target="_blank" title="softsimon">
|
||||||
|
<img class="image" src="/resources/profile/softsimon.jpg" />
|
||||||
|
<span>softsimon</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/wiz" target="_blank" title="wiz">
|
||||||
|
<img class="image" src="/resources/profile/wiz.png" />
|
||||||
|
<span>wiz</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="copyright">
|
||||||
|
<div class="title">
|
||||||
|
Copyright (c) 2019-2021<br />
|
||||||
|
The Mempool Open Source Project
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
This program is free software; you can redistribute it and/or modify it under the terms of (at your option) either:<br>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
1) the <a href="https://www.gnu.org/licenses/agpl-3.0-standalone.html">GNU Affero General Public License</a> as published by the Free Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on <https://mempool.space/about>; or<br>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
2) the <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU General Public License</a> as published by the Free Software Foundation, either version 3 of the License or any later version approved by a proxy statement published on <https://mempool.space/about>.<br>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the full license terms for more details.<br>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
mempool.space™, The Mempool Open Source Project™, and the Mempool block logo are trademarks of Mempool Space K.K. in Japan.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-links">
|
||||||
<a href="/3rdpartylicenses.txt">Third-party Licenses</a>
|
<a href="/3rdpartylicenses.txt">Third-party Licenses</a>
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<div class="footer-version" *ngIf="officialMempoolSpace">
|
||||||
|
|
||||||
<div class="text-small text-center" *ngIf="officialMempoolSpace">
|
|
||||||
{{ (backendInfo$ | async)?.hostname }} (v{{ (backendInfo$ | async )?.version }}) [{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}]
|
{{ (backendInfo$ | async)?.hostname }} (v{{ (backendInfo$ | async )?.version }}) [{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}]
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loadingSponsors>
|
||||||
|
<br>
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</ng-template>
|
||||||
|
|||||||
@@ -1,50 +1,179 @@
|
|||||||
.qr-wrapper {
|
|
||||||
background-color: #FFF;
|
|
||||||
padding: 10px;
|
|
||||||
display: inline-block;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile_photo {
|
.about-page {
|
||||||
width: 80px;
|
text-align: center;
|
||||||
height: 80px;
|
|
||||||
background-size: 100%, 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin: 25px;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile_img {
|
.image {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
border-radius: 50%;
|
background-size: 100%, 100%;
|
||||||
border: 0;
|
border-radius: 50%;
|
||||||
}
|
margin: 25px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.community_sponsor {
|
.intro {
|
||||||
margin: 6px;
|
margin: 25px auto 30px;
|
||||||
}
|
width: 250px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.logo {
|
||||||
|
height: 62.5px;
|
||||||
|
width: 250px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.version {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.enterprise_sponsor {
|
.about-text {
|
||||||
margin-left: 40px;
|
max-width: 550px;
|
||||||
margin-right: 40px;
|
margin: auto;
|
||||||
}
|
padding: 10px 15px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.project_contributor {
|
.social-icons {
|
||||||
width: 130px;
|
a {
|
||||||
margin: 25px auto;
|
margin: auto 10px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.text-small {
|
.alliances,
|
||||||
font-size: 12px;
|
.enterprise-sponsor,
|
||||||
}
|
.community-integrations-sponsor,
|
||||||
|
.maintainers {
|
||||||
|
margin-top: 68px;
|
||||||
|
margin-bottom: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
.info-group {
|
.maintainers {
|
||||||
max-width: 400px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.community-sponsor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.wrapper {
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
max-width: 250px;
|
||||||
|
margin: auto;
|
||||||
|
height: 45px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.required {
|
.alliances {
|
||||||
color: #FF0000;
|
margin-bottom: 100px;
|
||||||
font-weight: bold;
|
a {
|
||||||
|
&:nth-child(3) {
|
||||||
|
position: relative;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 15px auto;
|
||||||
|
height: 62px;
|
||||||
|
@media (min-width: 425px) {
|
||||||
|
margin: 15px 60px;
|
||||||
|
}
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
margin: 15px 120px;
|
||||||
|
}
|
||||||
|
@media (min-width: 850px) {
|
||||||
|
margin: 50px 30px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.liquid {
|
||||||
|
top: 7px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.copa {
|
||||||
|
height: auto;
|
||||||
|
top: 23px;
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.bisq {
|
||||||
|
top: 3px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.enterprise-sponsor,
|
||||||
|
.contributors,
|
||||||
|
.community-sponsor,
|
||||||
|
.community-integrations-sponsor,
|
||||||
|
.maintainers {
|
||||||
|
.wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
img {
|
||||||
|
box-shadow: 0px 0px 20px #1bd8f4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img, span{
|
||||||
|
display: block;
|
||||||
|
transition: 150ms all;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
margin: 40px 29px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-sponsor .wrapper {
|
||||||
|
margin: 10px auto 20px;
|
||||||
|
a img {
|
||||||
|
margin: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
text-align: left;
|
||||||
|
max-width: 620px;
|
||||||
|
padding: 0px 15px;
|
||||||
|
margin: auto;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 87.5%;
|
||||||
|
color: #e83e8c;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-family: SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 30px;
|
||||||
|
li {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 15px auto 0px;
|
||||||
|
&:last-child {
|
||||||
|
margin: 20px auto 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-version {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,42 +1,33 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|
||||||
import { ApiService } from 'src/app/services/api.service';
|
import { ApiService } from 'src/app/services/api.service';
|
||||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
|
||||||
import { delay, retryWhen, switchMap, tap } from 'rxjs/operators';
|
|
||||||
import { IBackendInfo } from 'src/app/interfaces/websocket.interface';
|
import { IBackendInfo } from 'src/app/interfaces/websocket.interface';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-about',
|
selector: 'app-about',
|
||||||
templateUrl: './about.component.html',
|
templateUrl: './about.component.html',
|
||||||
styleUrls: ['./about.component.scss'],
|
styleUrls: ['./about.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AboutComponent implements OnInit, OnDestroy {
|
export class AboutComponent implements OnInit {
|
||||||
backendInfo$: Observable<IBackendInfo>;
|
backendInfo$: Observable<IBackendInfo>;
|
||||||
donationForm: FormGroup;
|
|
||||||
paymentForm: FormGroup;
|
|
||||||
donationStatus = 1;
|
|
||||||
sponsors$: Observable<any>;
|
sponsors$: Observable<any>;
|
||||||
contributors$: Observable<any>;
|
contributors$: Observable<any>;
|
||||||
donationObj: any;
|
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH;
|
||||||
sponsorsEnabled = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
|
||||||
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH.substr(0, 8);
|
|
||||||
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
|
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
|
||||||
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
||||||
sponsors = null;
|
showNavigateToSponsor = false;
|
||||||
contributors = null;
|
|
||||||
requestSubscription: Subscription | undefined;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private formBuilder: FormBuilder,
|
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private sanitizer: DomSanitizer,
|
private router: Router,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -44,59 +35,15 @@ export class AboutComponent implements OnInit, OnDestroy {
|
|||||||
this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`);
|
this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`);
|
||||||
this.websocketService.want(['blocks']);
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
this.donationForm = this.formBuilder.group({
|
this.sponsors$ = this.apiService.getDonation$();
|
||||||
amount: [0.01, [Validators.min(0.001), Validators.required]],
|
this.contributors$ = this.apiService.getContributor$();
|
||||||
handle: [''],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.paymentForm = this.formBuilder.group({
|
|
||||||
'method': 'chain'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.apiService.getDonation$()
|
|
||||||
.subscribe((sponsors) => {
|
|
||||||
this.sponsors = sponsors;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.apiService.getContributor$()
|
|
||||||
.subscribe((contributors) => {
|
|
||||||
this.contributors = contributors;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
sponsor() {
|
||||||
if (this.requestSubscription) {
|
if (this.officialMempoolSpace) {
|
||||||
this.requestSubscription.unsubscribe();
|
this.router.navigateByUrl('/sponsor');
|
||||||
|
} else {
|
||||||
|
this.showNavigateToSponsor = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submitDonation() {
|
|
||||||
if (this.donationForm.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.requestSubscription = this.apiService.requestDonation$(
|
|
||||||
this.donationForm.get('amount').value,
|
|
||||||
this.donationForm.get('handle').value
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
tap((response) => {
|
|
||||||
this.donationObj = response;
|
|
||||||
this.donationStatus = 3;
|
|
||||||
}),
|
|
||||||
switchMap(() => this.apiService.checkDonation$(this.donationObj.id)
|
|
||||||
.pipe(
|
|
||||||
retryWhen((errors) => errors.pipe(delay(2000)))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).subscribe(() => {
|
|
||||||
this.donationStatus = 4;
|
|
||||||
if (this.donationForm.get('handle').value) {
|
|
||||||
this.sponsors.unshift({ handle: this.donationForm.get('handle').value });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bypassSecurityTrustUrl(text: string): SafeUrl {
|
|
||||||
return this.sanitizer.bypassSecurityTrustUrl(text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<h1 class="float-left" i18n="shared.address">Address</h1>
|
<h1 i18n="shared.address">Address</h1>
|
||||||
<a [routerLink]="['/address/' | relativeUrl, addressString]" style="line-height: 56px; margin-left: 10px;">
|
<div class="tx-link">
|
||||||
<span class="d-inline d-lg-none">{{ addressString | shortenString : 18 }}</span>
|
<a [routerLink]="['/address/' | relativeUrl, addressString]" >
|
||||||
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
<span class="d-inline d-lg-none">{{ addressString | shortenString : 18 }}</span>
|
||||||
</a>
|
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
||||||
<app-clipboard [text]="addressString"></app-clipboard>
|
</a>
|
||||||
|
<app-clipboard [text]="addressString"></app-clipboard>
|
||||||
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
@@ -13,7 +15,7 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col-md">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template [ngIf]="!address.electrum">
|
<ng-template [ngIf]="!address.electrum">
|
||||||
@@ -28,13 +30,13 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.balance">Balance</td>
|
<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> (<app-fiat [value]="receieved - sent"></app-fiat>)</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>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-100 d-block d-md-none"></div>
|
<div class="w-100 d-block d-md-none"></div>
|
||||||
<div class="col qrcode-col">
|
<div class="col-md qrcode-col">
|
||||||
<div class="qr-wrapper">
|
<div class="qr-wrapper">
|
||||||
<app-qrcode [data]="address.address"></app-qrcode>
|
<app-qrcode [data]="address.address"></app-qrcode>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,6 +80,11 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@@ -114,10 +121,13 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
||||||
<br>
|
<br>
|
||||||
<i>{{ error.error }}</i>
|
<ng-template #displayServerError><i>{{ error.error }}</i></ng-template>
|
||||||
<ng-template [ngIf]="error.status === 413 || error.status === 405">
|
<ng-template [ngIf]="error.status === 413 || error.status === 405" [ngIfElse]="displayServerError">
|
||||||
<br><br>
|
<ng-container i18n="Electrum server limit exceeded error">
|
||||||
Consider viewing this address on the official Mempool website instead:
|
<i>The number of transactions on this address exceeds the Electrum server limit</i>
|
||||||
|
<br><br>
|
||||||
|
Consider viewing this address on the official Mempool website instead:
|
||||||
|
</ng-container>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://mempool.space/address/{{ addressString }}" target="_blank">https://mempool.space/address/{{ addressString }}</a>
|
<a href="https://mempool.space/address/{{ addressString }}" target="_blank">https://mempool.space/address/{{ addressString }}</a>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -3,21 +3,76 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 25px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 576px) {
|
.qrcode-col {
|
||||||
.qrcode-col {
|
margin: 20px auto 10px;
|
||||||
text-align: right;
|
text-align: center;
|
||||||
|
@media (min-width: 992px){
|
||||||
|
margin: 0px auto 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 575.98px) {
|
|
||||||
.qrcode-col {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qrcode-col > div {
|
.fiat {
|
||||||
margin-top: 20px;
|
display: block;
|
||||||
margin-right: 0px;
|
@media (min-width: 992px){
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
tr td {
|
||||||
|
&:last-child {
|
||||||
|
text-align: right;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-link {
|
||||||
|
line-height: 56px;
|
||||||
|
margin-left: 0px;
|
||||||
|
top: -2px;
|
||||||
|
position: relative;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
line-height: 69px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row{
|
||||||
|
flex-direction: column;
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.mobile-bottomcol {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.details-table td:first-child {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-link {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
top: 14px;
|
||||||
|
position: relative;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
isLoadingAddress = true;
|
isLoadingAddress = true;
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
isLoadingTransactions = true;
|
isLoadingTransactions = true;
|
||||||
|
retryLoadmore = false;
|
||||||
error: any;
|
error: any;
|
||||||
mainSubscription: Subscription;
|
mainSubscription: Subscription;
|
||||||
addressLoadingStatus$: Observable<number>;
|
addressLoadingStatus$: Observable<number>;
|
||||||
@@ -183,12 +184,17 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isLoadingTransactions = true;
|
this.isLoadingTransactions = true;
|
||||||
|
this.retryLoadmore = false;
|
||||||
this.electrsApiService.getAddressTransactionsFromHash$(this.address.address, this.lastTransactionTxId)
|
this.electrsApiService.getAddressTransactionsFromHash$(this.address.address, this.lastTransactionTxId)
|
||||||
.subscribe((transactions: Transaction[]) => {
|
.subscribe((transactions: Transaction[]) => {
|
||||||
this.lastTransactionTxId = transactions[transactions.length - 1].txid;
|
this.lastTransactionTxId = transactions[transactions.length - 1].txid;
|
||||||
this.loadedConfirmedTxCount += transactions.length;
|
this.loadedConfirmedTxCount += transactions.length;
|
||||||
this.transactions = this.transactions.concat(transactions);
|
this.transactions = this.transactions.concat(transactions);
|
||||||
this.isLoadingTransactions = false;
|
this.isLoadingTransactions = false;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.isLoadingTransactions = false;
|
||||||
|
this.retryLoadmore = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
|
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
|
||||||
<span>{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
<span class="fiat">{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #viewFiatVin>
|
<ng-template #viewFiatVin>
|
||||||
<ng-template [ngIf]="network === 'liquid' && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
<ng-template [ngIf]="network === 'liquid' && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #default>
|
<ng-template #default>
|
||||||
‎{{ satoshis / 100000000 | number : digitsInfo }}
|
‎{{ satoshis / 100000000 | number : digitsInfo }}
|
||||||
<ng-template [ngIf]="network === 'liquid'">L-</ng-template>
|
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
||||||
<ng-template [ngIf]="network === 'signet'">s</ng-template>BTC
|
<ng-template [ngIf]="network === 'signet'">s</ng-template>BTC</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user