Compare commits
149 Commits
mononaut/s
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
a4e536d637 | ||
|
a232b6d3e8 | ||
|
4bcb3f6ab2 | ||
|
6f8901229a | ||
|
bb5b771128 | ||
|
6a17d665e0 | ||
|
f54bccf267 | ||
|
f909d6bca5 | ||
|
2407cbfd9a | ||
|
ec7af86142 | ||
|
5f761098ae | ||
|
b2456d104c | ||
|
edc5593792 | ||
|
85c78a448d | ||
|
60d548df46 | ||
|
0674f3a3ee | ||
|
210b632720 | ||
|
fc6c97172b | ||
|
a074c4b2c3 | ||
|
9e8a35f4a9 | ||
|
7b581b2ac3 | ||
|
1a62c867de | ||
|
470a6a7534 | ||
|
8bd7849b9d | ||
|
43393f7227 | ||
|
f67f946723 | ||
|
a0d0ee230e | ||
|
edf7798587 | ||
|
b826227b36 | ||
|
0924f6184b | ||
|
24d0ed4ced | ||
|
9eb85200e0 | ||
|
a79c165b30 | ||
|
7f07c5cbab | ||
|
c021a15d0b | ||
|
a2009aa322 | ||
|
e6965dac80 | ||
|
c4a7a2e781 | ||
|
03bea14f89 | ||
|
834f9a9f6d | ||
|
518297494f | ||
|
15e67bc77f | ||
|
ff383f9c58 | ||
|
ed44e27991 | ||
|
a869ea5ec4 | ||
|
fc4a0f7461 | ||
|
b3f21a10b9 | ||
|
4290e00376 | ||
|
3b91a1437a | ||
|
363fa3d877 | ||
|
2e44ea3f01 | ||
|
cac62765a1 | ||
|
23713a11c2 | ||
|
ff4aca8370 | ||
|
469faf7456 | ||
|
665a12a040 | ||
|
b42431f14a | ||
|
69dfcbe6fa | ||
|
b454fa09d2 | ||
|
019101862f | ||
|
5aeaa68259 | ||
|
e01898a4c5 | ||
|
817076fcbd | ||
|
0568a8c6c1 | ||
|
e53e810a55 | ||
|
e2c44b6c62 | ||
|
36b691e25b | ||
|
778837322d | ||
|
4a14e8d921 | ||
|
4e735cc8b0 | ||
|
4520e3fdf2 | ||
|
390bbf1097 | ||
|
bd8c1efc8e | ||
|
8fbc497a58 | ||
|
003956fd16 | ||
|
227d99e990 | ||
|
3d1aacbd66 | ||
|
1098d2fe3c | ||
|
f59e95fcc8 | ||
|
7f6399093e | ||
|
e9e8b0c758 | ||
|
517a30d2b0 | ||
|
7e766cc28d | ||
|
34099e3861 | ||
|
671b5ea2f2 | ||
|
caa2d83247 | ||
|
58e6a78579 | ||
|
703241acf0 | ||
|
6e8579363d | ||
|
b254be2f49 | ||
|
d6283c54ee | ||
|
9ba7172b5b | ||
|
cb4bf0611e | ||
|
3ea491ad13 | ||
|
eddd7344ad | ||
|
4ecf2eb679 | ||
|
34acbca4b9 | ||
|
8793fafa4c | ||
|
341da85c77 | ||
|
0d8f63feff | ||
|
e7af43efa2 | ||
|
aca2f2ec7d | ||
|
803b005880 | ||
|
204d54b189 | ||
|
c248544fe8 | ||
|
b65d00f289 | ||
|
f77dc68ec7 | ||
|
c4ec50b771 | ||
|
8529b99675 | ||
|
cd02d89235 | ||
|
4dcbccd9b2 | ||
|
6a4aeaf7ed | ||
|
6432f72664 | ||
|
f6ab2caaf9 | ||
|
3325db4883 | ||
|
0a255d7fe5 | ||
|
ca0a8aee49 | ||
|
4a4259fa7d | ||
|
faa83866fd | ||
|
54058b64ad | ||
|
f142b421f9 | ||
|
47cc58c610 | ||
|
21cdb7e3a1 | ||
|
c3686a5500 | ||
|
9fbbe4980d | ||
|
0611773647 | ||
|
7740908a4c | ||
|
68ea7c59f3 | ||
|
915f7a6c27 | ||
|
e18c572549 | ||
|
25133d8505 | ||
|
9f5666f410 | ||
|
6553344489 | ||
|
e77dd114f4 | ||
|
ff235760b2 | ||
|
37ddc29c2c | ||
|
a5c67b5ca1 | ||
|
f49152d09d | ||
|
464fabf137 | ||
|
c9b9485313 | ||
|
ba1ee15286 | ||
|
8670897a50 | ||
|
4ff2aad94a | ||
|
ac997f3d9e | ||
|
c8e967cc0c | ||
|
72ddb8c6a4 | ||
|
cdc4a430cd | ||
|
cbce49a8bf | ||
|
f2e7cf7441 |
72
.github/workflows/ci.yml
vendored
72
.github/workflows/ci.yml
vendored
@ -251,17 +251,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
module: ["mempool", "liquid"]
|
module: ["mempool", "liquid", "testnet4"]
|
||||||
include:
|
|
||||||
- module: "mempool"
|
|
||||||
spec: |
|
|
||||||
cypress/e2e/mainnet/*.spec.ts
|
|
||||||
cypress/e2e/signet/*.spec.ts
|
|
||||||
cypress/e2e/testnet4/*.spec.ts
|
|
||||||
- module: "liquid"
|
|
||||||
spec: |
|
|
||||||
cypress/e2e/liquid/liquid.spec.ts
|
|
||||||
cypress/e2e/liquidtestnet/liquidtestnet.spec.ts
|
|
||||||
|
|
||||||
name: E2E tests for ${{ matrix.module }}
|
name: E2E tests for ${{ matrix.module }}
|
||||||
steps:
|
steps:
|
||||||
@ -310,8 +300,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Unzip assets before building (src/resources)
|
- name: Unzip assets before building (src/resources)
|
||||||
run: unzip -o promo-video-assets.zip -d ${{ matrix.module }}/frontend/src/resources/promo-video
|
run: unzip -o promo-video-assets.zip -d ${{ matrix.module }}/frontend/src/resources/promo-video
|
||||||
|
|
||||||
|
# mempool
|
||||||
- name: Chrome browser tests (${{ matrix.module }})
|
- name: Chrome browser tests (${{ matrix.module }})
|
||||||
|
if: ${{ matrix.module == 'mempool' }}
|
||||||
uses: cypress-io/github-action@v5
|
uses: cypress-io/github-action@v5
|
||||||
with:
|
with:
|
||||||
tag: ${{ github.event_name }}
|
tag: ${{ github.event_name }}
|
||||||
@ -322,7 +314,9 @@ jobs:
|
|||||||
wait-on-timeout: 120
|
wait-on-timeout: 120
|
||||||
record: true
|
record: true
|
||||||
parallel: true
|
parallel: true
|
||||||
spec: ${{ matrix.spec }}
|
spec: |
|
||||||
|
cypress/e2e/mainnet/*.spec.ts
|
||||||
|
cypress/e2e/signet/*.spec.ts
|
||||||
group: Tests on Chrome (${{ matrix.module }})
|
group: Tests on Chrome (${{ matrix.module }})
|
||||||
browser: "chrome"
|
browser: "chrome"
|
||||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||||
@ -332,6 +326,56 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||||
|
|
||||||
|
# liquid
|
||||||
|
- name: Chrome browser tests (${{ matrix.module }})
|
||||||
|
if: ${{ matrix.module == 'liquid' }}
|
||||||
|
uses: cypress-io/github-action@v5
|
||||||
|
with:
|
||||||
|
tag: ${{ github.event_name }}
|
||||||
|
working-directory: ${{ matrix.module }}/frontend
|
||||||
|
build: npm run config:defaults:${{ matrix.module }}
|
||||||
|
start: npm run start:local-staging
|
||||||
|
wait-on: "http://localhost:4200"
|
||||||
|
wait-on-timeout: 120
|
||||||
|
record: true
|
||||||
|
parallel: true
|
||||||
|
spec: |
|
||||||
|
cypress/e2e/liquid/liquid.spec.ts
|
||||||
|
cypress/e2e/liquidtestnet/liquidtestnet.spec.ts
|
||||||
|
group: Tests on Chrome (${{ matrix.module }})
|
||||||
|
browser: "chrome"
|
||||||
|
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||||
|
env:
|
||||||
|
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||||
|
|
||||||
|
# testnet
|
||||||
|
- name: Chrome browser tests (${{ matrix.module }})
|
||||||
|
if: ${{ matrix.module == 'testnet4' }}
|
||||||
|
uses: cypress-io/github-action@v5
|
||||||
|
with:
|
||||||
|
tag: ${{ github.event_name }}
|
||||||
|
working-directory: ${{ matrix.module }}/frontend
|
||||||
|
build: npm run config:defaults:mempool
|
||||||
|
start: npm run start:local-staging
|
||||||
|
wait-on: "http://localhost:4200"
|
||||||
|
wait-on-timeout: 120
|
||||||
|
record: true
|
||||||
|
parallel: true
|
||||||
|
spec: |
|
||||||
|
cypress/e2e/testnet4/*.spec.ts
|
||||||
|
group: Tests on Chrome (${{ matrix.module }})
|
||||||
|
browser: "chrome"
|
||||||
|
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||||
|
env:
|
||||||
|
CYPRESS_REROUTE_TESTNET: true
|
||||||
|
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
|
||||||
|
|
||||||
validate_docker_json:
|
validate_docker_json:
|
||||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
@ -359,4 +403,4 @@ jobs:
|
|||||||
- name: Validate JSON syntax
|
- name: Validate JSON syntax
|
||||||
run: |
|
run: |
|
||||||
cat mempool-config.json | jq
|
cat mempool-config.json | jq
|
||||||
working-directory: docker/docker/backend
|
working-directory: docker/docker/backend
|
@ -7,7 +7,7 @@ const config: Config.InitialOptions = {
|
|||||||
automock: false,
|
automock: false,
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
collectCoverageFrom: ["./src/**/**.ts"],
|
collectCoverageFrom: ["./src/**/**.ts"],
|
||||||
coverageProvider: "babel",
|
coverageProvider: "v8",
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
lines: 1
|
lines: 1
|
||||||
|
@ -155,6 +155,10 @@
|
|||||||
"API": "https://mempool.space/api/v1/services",
|
"API": "https://mempool.space/api/v1/services",
|
||||||
"ACCELERATIONS": false
|
"ACCELERATIONS": false
|
||||||
},
|
},
|
||||||
|
"STRATUM": {
|
||||||
|
"ENABLED": false,
|
||||||
|
"API": "http://localhost:1234"
|
||||||
|
},
|
||||||
"FIAT_PRICE": {
|
"FIAT_PRICE": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"PAID": false,
|
"PAID": false,
|
||||||
|
55
backend/package-lock.json
generated
55
backend/package-lock.json
generated
@ -10,7 +10,6 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GNU Affero General Public License v3.0",
|
"license": "GNU Affero General Public License v3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
|
||||||
"@mempool/electrum-client": "1.1.9",
|
"@mempool/electrum-client": "1.1.9",
|
||||||
"@types/node": "^18.15.3",
|
"@types/node": "^18.15.3",
|
||||||
"axios": "1.7.2",
|
"axios": "1.7.2",
|
||||||
@ -18,7 +17,7 @@
|
|||||||
"crypto-js": "~4.2.0",
|
"crypto-js": "~4.2.0",
|
||||||
"express": "~4.21.1",
|
"express": "~4.21.1",
|
||||||
"maxmind": "~4.3.11",
|
"maxmind": "~4.3.11",
|
||||||
"mysql2": "~3.11.0",
|
"mysql2": "~3.12.0",
|
||||||
"redis": "^4.7.0",
|
"redis": "^4.7.0",
|
||||||
"rust-gbt": "file:./rust-gbt",
|
"rust-gbt": "file:./rust-gbt",
|
||||||
"socks-proxy-agent": "~7.0.0",
|
"socks-proxy-agent": "~7.0.0",
|
||||||
@ -26,8 +25,6 @@
|
|||||||
"ws": "~8.18.0"
|
"ws": "~8.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
|
||||||
"@babel/core": "^7.25.2",
|
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
@ -6000,6 +5997,21 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lru.min": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0",
|
||||||
|
"deno": ">=1.30.0",
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wellwelwel"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/make-dir": {
|
"node_modules/make-dir": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||||
@ -6161,16 +6173,17 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/mysql2": {
|
"node_modules/mysql2": {
|
||||||
"version": "3.11.0",
|
"version": "3.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz",
|
||||||
"integrity": "sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==",
|
"integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws-ssl-profiles": "^1.1.1",
|
"aws-ssl-profiles": "^1.1.1",
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
"generate-function": "^2.3.1",
|
"generate-function": "^2.3.1",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"long": "^5.2.1",
|
"long": "^5.2.1",
|
||||||
"lru-cache": "^8.0.0",
|
"lru.min": "^1.0.0",
|
||||||
"named-placeholders": "^1.1.3",
|
"named-placeholders": "^1.1.3",
|
||||||
"seq-queue": "^0.0.5",
|
"seq-queue": "^0.0.5",
|
||||||
"sqlstring": "^2.3.2"
|
"sqlstring": "^2.3.2"
|
||||||
@ -6190,14 +6203,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mysql2/node_modules/lru-cache": {
|
|
||||||
"version": "8.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
|
|
||||||
"integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/named-placeholders": {
|
"node_modules/named-placeholders": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||||
@ -12213,6 +12218,11 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lru.min": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q=="
|
||||||
|
},
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||||
@ -12327,16 +12337,16 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"mysql2": {
|
"mysql2": {
|
||||||
"version": "3.11.0",
|
"version": "3.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz",
|
||||||
"integrity": "sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==",
|
"integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"aws-ssl-profiles": "^1.1.1",
|
"aws-ssl-profiles": "^1.1.1",
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
"generate-function": "^2.3.1",
|
"generate-function": "^2.3.1",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"long": "^5.2.1",
|
"long": "^5.2.1",
|
||||||
"lru-cache": "^8.0.0",
|
"lru.min": "^1.0.0",
|
||||||
"named-placeholders": "^1.1.3",
|
"named-placeholders": "^1.1.3",
|
||||||
"seq-queue": "^0.0.5",
|
"seq-queue": "^0.0.5",
|
||||||
"sqlstring": "^2.3.2"
|
"sqlstring": "^2.3.2"
|
||||||
@ -12349,11 +12359,6 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"lru-cache": {
|
|
||||||
"version": "8.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
|
|
||||||
"integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA=="
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -39,7 +39,6 @@
|
|||||||
"prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
|
"prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
|
||||||
"@mempool/electrum-client": "1.1.9",
|
"@mempool/electrum-client": "1.1.9",
|
||||||
"@types/node": "^18.15.3",
|
"@types/node": "^18.15.3",
|
||||||
"axios": "1.7.2",
|
"axios": "1.7.2",
|
||||||
@ -47,7 +46,7 @@
|
|||||||
"crypto-js": "~4.2.0",
|
"crypto-js": "~4.2.0",
|
||||||
"express": "~4.21.1",
|
"express": "~4.21.1",
|
||||||
"maxmind": "~4.3.11",
|
"maxmind": "~4.3.11",
|
||||||
"mysql2": "~3.11.0",
|
"mysql2": "~3.12.0",
|
||||||
"rust-gbt": "file:./rust-gbt",
|
"rust-gbt": "file:./rust-gbt",
|
||||||
"redis": "^4.7.0",
|
"redis": "^4.7.0",
|
||||||
"socks-proxy-agent": "~7.0.0",
|
"socks-proxy-agent": "~7.0.0",
|
||||||
@ -55,8 +54,6 @@
|
|||||||
"ws": "~8.18.0"
|
"ws": "~8.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
|
||||||
"@babel/core": "^7.25.2",
|
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
|
@ -151,5 +151,9 @@
|
|||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"PAID": false,
|
"PAID": false,
|
||||||
"API_KEY": "__MEMPOOL_CURRENCY_API_KEY__"
|
"API_KEY": "__MEMPOOL_CURRENCY_API_KEY__"
|
||||||
|
},
|
||||||
|
"STRATUM": {
|
||||||
|
"ENABLED": false,
|
||||||
|
"API": "http://localhost:1234"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,11 @@ describe('Mempool Backend Config', () => {
|
|||||||
PAID: false,
|
PAID: false,
|
||||||
API_KEY: '',
|
API_KEY: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(config.STRATUM).toStrictEqual({
|
||||||
|
ENABLED: false,
|
||||||
|
API: 'http://localhost:1234',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import transactionRepository from '../../repositories/TransactionRepository';
|
|||||||
import rbfCache from '../rbf-cache';
|
import rbfCache from '../rbf-cache';
|
||||||
import { calculateMempoolTxCpfp } from '../cpfp';
|
import { calculateMempoolTxCpfp } from '../cpfp';
|
||||||
import { handleError } from '../../utils/api';
|
import { handleError } from '../../utils/api';
|
||||||
|
import poolsUpdater from '../../tasks/pools-updater';
|
||||||
|
|
||||||
const TXID_REGEX = /^[a-f0-9]{64}$/i;
|
const TXID_REGEX = /^[a-f0-9]{64}$/i;
|
||||||
const BLOCK_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
const BLOCK_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
||||||
@ -56,6 +57,10 @@ class BitcoinRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
||||||
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage)
|
||||||
|
// Internal routes
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/definition/list', this.getBlockDefinitionHashes)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/definition/current', this.getCurrentBlockDefinitionHash)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'internal/blocks/:definitionHash', this.getBlocksByDefinitionHash)
|
||||||
;
|
;
|
||||||
|
|
||||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
@ -739,6 +744,52 @@ class BitcoinRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getBlockDefinitionHashes(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const result = await blocks.$getBlockDefinitionHashes();
|
||||||
|
if (!result) {
|
||||||
|
handleError(req, res, 503, `Service Temporarily Unavailable`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.setHeader('content-type', 'application/json');
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getCurrentBlockDefinitionHash(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const currentSha = await poolsUpdater.getShaFromDb();
|
||||||
|
if (!currentSha) {
|
||||||
|
handleError(req, res, 503, `Service Temporarily Unavailable`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.setHeader('content-type', 'text/plain');
|
||||||
|
res.send(currentSha);
|
||||||
|
} catch (e) {
|
||||||
|
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getBlocksByDefinitionHash(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (typeof(req.params.definitionHash) !== 'string') {
|
||||||
|
res.status(400).send('Parameter "hash" must be a valid string');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const blocksHash = await blocks.$getBlocksByDefinitionHash(req.params.definitionHash as string);
|
||||||
|
if (!blocksHash) {
|
||||||
|
handleError(req, res, 503, `Service Temporarily Unavailable`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.setHeader('content-type', 'application/json');
|
||||||
|
res.send(blocksHash);
|
||||||
|
} catch (e) {
|
||||||
|
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getBlockTipHeight(req: Request, res: Response) {
|
private getBlockTipHeight(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const result = blocks.getCurrentBlockHeight();
|
const result = blocks.getCurrentBlockHeight();
|
||||||
|
@ -33,8 +33,8 @@ import AccelerationRepository from '../repositories/AccelerationRepository';
|
|||||||
import { calculateFastBlockCpfp, calculateGoodBlockCpfp } from './cpfp';
|
import { calculateFastBlockCpfp, calculateGoodBlockCpfp } from './cpfp';
|
||||||
import mempool from './mempool';
|
import mempool from './mempool';
|
||||||
import CpfpRepository from '../repositories/CpfpRepository';
|
import CpfpRepository from '../repositories/CpfpRepository';
|
||||||
import accelerationApi from './services/acceleration';
|
|
||||||
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
|
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
|
||||||
|
import database from '../database';
|
||||||
|
|
||||||
class Blocks {
|
class Blocks {
|
||||||
private blocks: BlockExtended[] = [];
|
private blocks: BlockExtended[] = [];
|
||||||
@ -1462,6 +1462,36 @@ class Blocks {
|
|||||||
// not a fatal error, we'll try again next time the indexer runs
|
// not a fatal error, we'll try again next time the indexer runs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getBlockDefinitionHashes(): Promise<string[] | null> {
|
||||||
|
try {
|
||||||
|
const [rows]: any = await database.query(`SELECT DISTINCT(definition_hash) FROM blocks`);
|
||||||
|
if (rows && Array.isArray(rows)) {
|
||||||
|
return rows.map(r => r.definition_hash);
|
||||||
|
} else {
|
||||||
|
logger.debug(`Unable to retreive list of blocks.definition_hash from db (no result)`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`Unable to retreive list of blocks.definition_hash from db (exception: ${e})`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $getBlocksByDefinitionHash(definitionHash: string): Promise<string[] | null> {
|
||||||
|
try {
|
||||||
|
const [rows]: any = await database.query(`SELECT hash FROM blocks WHERE definition_hash = ?`, [definitionHash]);
|
||||||
|
if (rows && Array.isArray(rows)) {
|
||||||
|
return rows.map(r => r.hash);
|
||||||
|
} else {
|
||||||
|
logger.debug(`Unable to retreive list of blocks for definition hash ${definitionHash} from db (no result)`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`Unable to retreive list of blocks for definition hash ${definitionHash} from db (exception: ${e})`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Blocks();
|
export default new Blocks();
|
||||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 94;
|
private static currentVersion = 95;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -1118,6 +1118,18 @@ class DatabaseMigration {
|
|||||||
}
|
}
|
||||||
await this.updateToSchemaVersion(94);
|
await this.updateToSchemaVersion(94);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blocks pools-v2.json hash
|
||||||
|
if (databaseSchemaVersion < 95) {
|
||||||
|
let poolJsonSha = 'f737d86571d190cf1a1a3cf5fd86b33ba9624254';
|
||||||
|
const [poolJsonShaDb]: any[] = await DB.query(`SELECT string FROM state WHERE name = 'pools_json_sha'`);
|
||||||
|
if (poolJsonShaDb?.length > 0) {
|
||||||
|
poolJsonSha = poolJsonShaDb[0].string;
|
||||||
|
}
|
||||||
|
await this.$executeQuery(`ALTER TABLE blocks ADD definition_hash varchar(255) NOT NULL DEFAULT "${poolJsonSha}"`);
|
||||||
|
await this.$executeQuery('ALTER TABLE blocks ADD INDEX `definition_hash` (`definition_hash`)');
|
||||||
|
await this.updateToSchemaVersion(95);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,15 +19,6 @@ class PoolsParser {
|
|||||||
'addresses': '[]',
|
'addresses': '[]',
|
||||||
'slug': 'unknown'
|
'slug': 'unknown'
|
||||||
};
|
};
|
||||||
private uniqueLogs: string[] = [];
|
|
||||||
|
|
||||||
private uniqueLog(loggerFunction: any, msg: string): void {
|
|
||||||
if (this.uniqueLogs.includes(msg)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.uniqueLogs.push(msg);
|
|
||||||
loggerFunction(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setMiningPools(pools): void {
|
public setMiningPools(pools): void {
|
||||||
for (const pool of pools) {
|
for (const pool of pools) {
|
||||||
|
@ -119,7 +119,11 @@ class RbfCache {
|
|||||||
|
|
||||||
|
|
||||||
public add(replaced: MempoolTransactionExtended[], newTxExtended: MempoolTransactionExtended): void {
|
public add(replaced: MempoolTransactionExtended[], newTxExtended: MempoolTransactionExtended): void {
|
||||||
if (!newTxExtended || !replaced?.length || this.txs.has(newTxExtended.txid)) {
|
if ( !newTxExtended
|
||||||
|
|| !replaced?.length
|
||||||
|
|| this.txs.has(newTxExtended.txid)
|
||||||
|
|| !(replaced.some(tx => !this.replacedBy.has(tx.txid)))
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
backend/src/api/services/stratum.ts
Normal file
105
backend/src/api/services/stratum.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { WebSocket } from 'ws';
|
||||||
|
import logger from '../../logger';
|
||||||
|
import config from '../../config';
|
||||||
|
import websocketHandler from '../websocket-handler';
|
||||||
|
|
||||||
|
export interface StratumJob {
|
||||||
|
pool: number;
|
||||||
|
height: number;
|
||||||
|
coinbase: string;
|
||||||
|
scriptsig: string;
|
||||||
|
reward: number;
|
||||||
|
jobId: string;
|
||||||
|
extraNonce: string;
|
||||||
|
extraNonce2Size: number;
|
||||||
|
prevHash: string;
|
||||||
|
coinbase1: string;
|
||||||
|
coinbase2: string;
|
||||||
|
merkleBranches: string[];
|
||||||
|
version: string;
|
||||||
|
bits: string;
|
||||||
|
time: string;
|
||||||
|
timestamp: number;
|
||||||
|
cleanJobs: boolean;
|
||||||
|
received: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStratumJob(obj: any): obj is StratumJob {
|
||||||
|
return obj
|
||||||
|
&& typeof obj === 'object'
|
||||||
|
&& 'pool' in obj
|
||||||
|
&& 'prevHash' in obj
|
||||||
|
&& 'height' in obj
|
||||||
|
&& 'received' in obj
|
||||||
|
&& 'version' in obj
|
||||||
|
&& 'timestamp' in obj
|
||||||
|
&& 'bits' in obj
|
||||||
|
&& 'merkleBranches' in obj
|
||||||
|
&& 'cleanJobs' in obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
class StratumApi {
|
||||||
|
private ws: WebSocket | null = null;
|
||||||
|
private runWebsocketLoop: boolean = false;
|
||||||
|
private startedWebsocketLoop: boolean = false;
|
||||||
|
private websocketConnected: boolean = false;
|
||||||
|
private jobs: Record<string, StratumJob> = {};
|
||||||
|
|
||||||
|
public constructor() {}
|
||||||
|
|
||||||
|
public getJobs(): Record<string, StratumJob> {
|
||||||
|
return this.jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleWebsocketMessage(msg: any): void {
|
||||||
|
if (isStratumJob(msg)) {
|
||||||
|
this.jobs[msg.pool] = msg;
|
||||||
|
websocketHandler.handleNewStratumJob(this.jobs[msg.pool]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connectWebsocket(): Promise<void> {
|
||||||
|
if (!config.STRATUM.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.runWebsocketLoop = true;
|
||||||
|
if (this.startedWebsocketLoop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (this.runWebsocketLoop) {
|
||||||
|
this.startedWebsocketLoop = true;
|
||||||
|
if (!this.ws) {
|
||||||
|
this.ws = new WebSocket(`${config.STRATUM.API}`);
|
||||||
|
this.websocketConnected = true;
|
||||||
|
|
||||||
|
this.ws.on('open', () => {
|
||||||
|
logger.info('Stratum websocket opened');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('error', (error) => {
|
||||||
|
logger.err('Stratum websocket error: ' + error);
|
||||||
|
this.ws = null;
|
||||||
|
this.websocketConnected = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('close', () => {
|
||||||
|
logger.info('Stratum websocket closed');
|
||||||
|
this.ws = null;
|
||||||
|
this.websocketConnected = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('message', (data, isBinary) => {
|
||||||
|
try {
|
||||||
|
const parsedMsg = JSON.parse((isBinary ? data : data.toString()) as string);
|
||||||
|
this.handleWebsocketMessage(parsedMsg);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Failed to parse stratum websocket message: ' + (e instanceof Error ? e.message : e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new StratumApi();
|
@ -38,6 +38,7 @@ interface AddressTransactions {
|
|||||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||||
import { calculateMempoolTxCpfp } from './cpfp';
|
import { calculateMempoolTxCpfp } from './cpfp';
|
||||||
import { getRecentFirstSeen } from '../utils/file-read';
|
import { getRecentFirstSeen } from '../utils/file-read';
|
||||||
|
import stratumApi, { StratumJob } from './services/stratum';
|
||||||
|
|
||||||
// valid 'want' subscriptions
|
// valid 'want' subscriptions
|
||||||
const wantable = [
|
const wantable = [
|
||||||
@ -403,6 +404,16 @@ class WebsocketHandler {
|
|||||||
delete client['track-mempool'];
|
delete client['track-mempool'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parsedMessage && parsedMessage['track-stratum'] != null) {
|
||||||
|
if (parsedMessage['track-stratum']) {
|
||||||
|
const sub = parsedMessage['track-stratum'];
|
||||||
|
client['track-stratum'] = sub;
|
||||||
|
response['stratumJobs'] = this.socketData['stratumJobs'];
|
||||||
|
} else {
|
||||||
|
client['track-stratum'] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
@ -1384,6 +1395,23 @@ class WebsocketHandler {
|
|||||||
await statistics.runStatistics();
|
await statistics.runStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public handleNewStratumJob(job: StratumJob): void {
|
||||||
|
this.updateSocketDataFields({ 'stratumJobs': stratumApi.getJobs() });
|
||||||
|
|
||||||
|
for (const server of this.webSocketServers) {
|
||||||
|
server.clients.forEach((client) => {
|
||||||
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (client['track-stratum'] && (client['track-stratum'] === 'all' || client['track-stratum'] === job.pool)) {
|
||||||
|
client.send(JSON.stringify({
|
||||||
|
'stratumJob': job
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// takes a dictionary of JSON serialized values
|
// takes a dictionary of JSON serialized values
|
||||||
// and zips it together into a valid JSON object
|
// and zips it together into a valid JSON object
|
||||||
private serializeResponse(response): string {
|
private serializeResponse(response): string {
|
||||||
|
@ -165,6 +165,10 @@ interface IConfig {
|
|||||||
WALLETS: {
|
WALLETS: {
|
||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
WALLETS: string[];
|
WALLETS: string[];
|
||||||
|
},
|
||||||
|
STRATUM: {
|
||||||
|
ENABLED: boolean;
|
||||||
|
API: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,6 +336,10 @@ const defaults: IConfig = {
|
|||||||
'ENABLED': false,
|
'ENABLED': false,
|
||||||
'WALLETS': [],
|
'WALLETS': [],
|
||||||
},
|
},
|
||||||
|
'STRATUM': {
|
||||||
|
'ENABLED': false,
|
||||||
|
'API': 'http://localhost:1234',
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Config implements IConfig {
|
class Config implements IConfig {
|
||||||
@ -354,6 +362,7 @@ class Config implements IConfig {
|
|||||||
REDIS: IConfig['REDIS'];
|
REDIS: IConfig['REDIS'];
|
||||||
FIAT_PRICE: IConfig['FIAT_PRICE'];
|
FIAT_PRICE: IConfig['FIAT_PRICE'];
|
||||||
WALLETS: IConfig['WALLETS'];
|
WALLETS: IConfig['WALLETS'];
|
||||||
|
STRATUM: IConfig['STRATUM'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const configs = this.merge(configFromFile, defaults);
|
const configs = this.merge(configFromFile, defaults);
|
||||||
@ -376,6 +385,7 @@ class Config implements IConfig {
|
|||||||
this.REDIS = configs.REDIS;
|
this.REDIS = configs.REDIS;
|
||||||
this.FIAT_PRICE = configs.FIAT_PRICE;
|
this.FIAT_PRICE = configs.FIAT_PRICE;
|
||||||
this.WALLETS = configs.WALLETS;
|
this.WALLETS = configs.WALLETS;
|
||||||
|
this.STRATUM = configs.STRATUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
merge = (...objects: object[]): IConfig => {
|
merge = (...objects: object[]): IConfig => {
|
||||||
|
@ -48,6 +48,7 @@ import accelerationRoutes from './api/acceleration/acceleration.routes';
|
|||||||
import aboutRoutes from './api/about.routes';
|
import aboutRoutes from './api/about.routes';
|
||||||
import mempoolBlocks from './api/mempool-blocks';
|
import mempoolBlocks from './api/mempool-blocks';
|
||||||
import walletApi from './api/services/wallets';
|
import walletApi from './api/services/wallets';
|
||||||
|
import stratumApi from './api/services/stratum';
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
private wss: WebSocket.Server | undefined;
|
private wss: WebSocket.Server | undefined;
|
||||||
@ -320,6 +321,9 @@ class Server {
|
|||||||
loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler));
|
loadingIndicators.setProgressChangedCallback(websocketHandler.handleLoadingChanged.bind(websocketHandler));
|
||||||
|
|
||||||
accelerationApi.connectWebsocket();
|
accelerationApi.connectWebsocket();
|
||||||
|
if (config.STRATUM.ENABLED) {
|
||||||
|
stratumApi.connectWebsocket();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setUpHttpApiRoutes(): void {
|
setUpHttpApiRoutes(): void {
|
||||||
|
@ -325,6 +325,8 @@ export interface BlockExtension {
|
|||||||
// Requires coinstatsindex, will be set to NULL otherwise
|
// Requires coinstatsindex, will be set to NULL otherwise
|
||||||
utxoSetSize: number | null;
|
utxoSetSize: number | null;
|
||||||
totalInputAmt: number | null;
|
totalInputAmt: number | null;
|
||||||
|
// pools-v2.json git hash
|
||||||
|
definitionHash: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +15,7 @@ import blocks from '../api/blocks';
|
|||||||
import BlocksAuditsRepository from './BlocksAuditsRepository';
|
import BlocksAuditsRepository from './BlocksAuditsRepository';
|
||||||
import transactionUtils from '../api/transaction-utils';
|
import transactionUtils from '../api/transaction-utils';
|
||||||
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
|
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
|
||||||
|
import poolsUpdater from '../tasks/pools-updater';
|
||||||
|
|
||||||
interface DatabaseBlock {
|
interface DatabaseBlock {
|
||||||
id: string;
|
id: string;
|
||||||
@ -114,16 +115,16 @@ class BlocksRepository {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const query = `INSERT INTO blocks(
|
const query = `INSERT INTO blocks(
|
||||||
height, hash, blockTimestamp, size,
|
height, hash, blockTimestamp, size,
|
||||||
weight, tx_count, coinbase_raw, difficulty,
|
weight, tx_count, coinbase_raw, difficulty,
|
||||||
pool_id, fees, fee_span, median_fee,
|
pool_id, fees, fee_span, median_fee,
|
||||||
reward, version, bits, nonce,
|
reward, version, bits, nonce,
|
||||||
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
|
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
|
||||||
median_timestamp, header, coinbase_address, coinbase_addresses,
|
median_timestamp, header, coinbase_address, coinbase_addresses,
|
||||||
coinbase_signature, utxoset_size, utxoset_change, avg_tx_size,
|
coinbase_signature, utxoset_size, utxoset_change, avg_tx_size,
|
||||||
total_inputs, total_outputs, total_input_amt, total_output_amt,
|
total_inputs, total_outputs, total_input_amt, total_output_amt,
|
||||||
fee_percentiles, segwit_total_txs, segwit_total_size, segwit_total_weight,
|
fee_percentiles, segwit_total_txs, segwit_total_size, segwit_total_weight,
|
||||||
median_fee_amt, coinbase_signature_ascii
|
median_fee_amt, coinbase_signature_ascii, definition_hash
|
||||||
) VALUE (
|
) VALUE (
|
||||||
?, ?, FROM_UNIXTIME(?), ?,
|
?, ?, FROM_UNIXTIME(?), ?,
|
||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
@ -134,7 +135,7 @@ class BlocksRepository {
|
|||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
?, ?
|
?, ?, ?
|
||||||
)`;
|
)`;
|
||||||
|
|
||||||
const poolDbId = await PoolsRepository.$getPoolByUniqueId(block.extras.pool.id);
|
const poolDbId = await PoolsRepository.$getPoolByUniqueId(block.extras.pool.id);
|
||||||
@ -181,6 +182,7 @@ class BlocksRepository {
|
|||||||
block.extras.segwitTotalWeight,
|
block.extras.segwitTotalWeight,
|
||||||
block.extras.medianFeeAmt,
|
block.extras.medianFeeAmt,
|
||||||
truncatedCoinbaseSignatureAscii,
|
truncatedCoinbaseSignatureAscii,
|
||||||
|
poolsUpdater.currentSha
|
||||||
];
|
];
|
||||||
|
|
||||||
await DB.query(query, params);
|
await DB.query(query, params);
|
||||||
@ -1013,9 +1015,9 @@ class BlocksRepository {
|
|||||||
public async $savePool(id: string, poolId: number): Promise<void> {
|
public async $savePool(id: string, poolId: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await DB.query(`
|
await DB.query(`
|
||||||
UPDATE blocks SET pool_id = ?
|
UPDATE blocks SET pool_id = ?, definition_hash = ?
|
||||||
WHERE hash = ?`,
|
WHERE hash = ?`,
|
||||||
[poolId, id]
|
[poolId, poolsUpdater.currentSha, id]
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot update block pool. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot update block pool. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
@ -88,8 +88,8 @@ class PoolsUpdater {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await DB.query('START TRANSACTION;');
|
await DB.query('START TRANSACTION;');
|
||||||
await poolsParser.migratePoolsJson();
|
|
||||||
await this.updateDBSha(githubSha);
|
await this.updateDBSha(githubSha);
|
||||||
|
await poolsParser.migratePoolsJson();
|
||||||
await DB.query('COMMIT;');
|
await DB.query('COMMIT;');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Could not migrate mining pools, rolling back. Exception: ${JSON.stringify(e)}`, this.tag);
|
logger.err(`Could not migrate mining pools, rolling back. Exception: ${JSON.stringify(e)}`, this.tag);
|
||||||
@ -121,7 +121,7 @@ class PoolsUpdater {
|
|||||||
/**
|
/**
|
||||||
* Fetch our latest pools-v2.json sha from the db
|
* Fetch our latest pools-v2.json sha from the db
|
||||||
*/
|
*/
|
||||||
private async getShaFromDb(): Promise<string | null> {
|
public async getShaFromDb(): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await DB.query('SELECT string FROM state WHERE name="pools_json_sha"');
|
const [rows]: any[] = await DB.query('SELECT string FROM state WHERE name="pools_json_sha"');
|
||||||
return (rows.length > 0 ? rows[0].string : null);
|
return (rows.length > 0 ? rows[0].string : null);
|
||||||
|
@ -148,6 +148,10 @@
|
|||||||
"API": "__MEMPOOL_SERVICES_API__",
|
"API": "__MEMPOOL_SERVICES_API__",
|
||||||
"ACCELERATIONS": __MEMPOOL_SERVICES_ACCELERATIONS__
|
"ACCELERATIONS": __MEMPOOL_SERVICES_ACCELERATIONS__
|
||||||
},
|
},
|
||||||
|
"STRATUM": {
|
||||||
|
"ENABLED": __STRATUM_ENABLED__,
|
||||||
|
"API": "__STRATUM_API__"
|
||||||
|
},
|
||||||
"REDIS": {
|
"REDIS": {
|
||||||
"ENABLED": __REDIS_ENABLED__,
|
"ENABLED": __REDIS_ENABLED__,
|
||||||
"UNIX_SOCKET_PATH": "__REDIS_UNIX_SOCKET_PATH__",
|
"UNIX_SOCKET_PATH": "__REDIS_UNIX_SOCKET_PATH__",
|
||||||
|
@ -149,6 +149,10 @@ __REPLICATION_SERVERS__=${REPLICATION_SERVERS:=[]}
|
|||||||
__MEMPOOL_SERVICES_API__=${MEMPOOL_SERVICES_API:="https://mempool.space/api/v1/services"}
|
__MEMPOOL_SERVICES_API__=${MEMPOOL_SERVICES_API:="https://mempool.space/api/v1/services"}
|
||||||
__MEMPOOL_SERVICES_ACCELERATIONS__=${MEMPOOL_SERVICES_ACCELERATIONS:=false}
|
__MEMPOOL_SERVICES_ACCELERATIONS__=${MEMPOOL_SERVICES_ACCELERATIONS:=false}
|
||||||
|
|
||||||
|
# STRATUM
|
||||||
|
__STRATUM_ENABLED__=${STRATUM_ENABLED:=false}
|
||||||
|
__STRATUM_API__=${STRATUM_API:="http://localhost:1234"}
|
||||||
|
|
||||||
# REDIS
|
# REDIS
|
||||||
__REDIS_ENABLED__=${REDIS_ENABLED:=false}
|
__REDIS_ENABLED__=${REDIS_ENABLED:=false}
|
||||||
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=""}
|
__REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=""}
|
||||||
@ -300,6 +304,10 @@ sed -i "s!__REPLICATION_SERVERS__!${__REPLICATION_SERVERS__}!g" mempool-config.j
|
|||||||
sed -i "s!__MEMPOOL_SERVICES_API__!${__MEMPOOL_SERVICES_API__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_SERVICES_API__!${__MEMPOOL_SERVICES_API__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_SERVICES_ACCELERATIONS__!${__MEMPOOL_SERVICES_ACCELERATIONS__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_SERVICES_ACCELERATIONS__!${__MEMPOOL_SERVICES_ACCELERATIONS__}!g" mempool-config.json
|
||||||
|
|
||||||
|
# STRATUM
|
||||||
|
sed -i "s!__STRATUM_ENABLED__!${__STRATUM_ENABLED__}!g" mempool-config.json
|
||||||
|
sed -i "s!__STRATUM_API__!${__STRATUM_API__}!g" mempool-config.json
|
||||||
|
|
||||||
# REDIS
|
# REDIS
|
||||||
sed -i "s!__REDIS_ENABLED__!${__REDIS_ENABLED__}!g" mempool-config.json
|
sed -i "s!__REDIS_ENABLED__!${__REDIS_ENABLED__}!g" mempool-config.json
|
||||||
sed -i "s!__REDIS_UNIX_SOCKET_PATH__!${__REDIS_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
sed -i "s!__REDIS_UNIX_SOCKET_PATH__!${__REDIS_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
||||||
|
@ -11,10 +11,14 @@ services:
|
|||||||
stop_grace_period: 1m
|
stop_grace_period: 1m
|
||||||
command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'"
|
command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'"
|
||||||
ports:
|
ports:
|
||||||
- 80:8080
|
- 8080:8080
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
MEMPOOL_BACKEND: "none"
|
MEMPOOL_BACKEND: "electrum"
|
||||||
|
ELECTRUM_HOST: "172.27.0.1"
|
||||||
|
ELECTRUM_PORT: "50001"
|
||||||
|
ELECTRUM_TLS_ENABLED: "false"
|
||||||
|
|
||||||
CORE_RPC_HOST: "172.27.0.1"
|
CORE_RPC_HOST: "172.27.0.1"
|
||||||
CORE_RPC_PORT: "8332"
|
CORE_RPC_PORT: "8332"
|
||||||
CORE_RPC_USERNAME: "mempool"
|
CORE_RPC_USERNAME: "mempool"
|
||||||
|
@ -45,6 +45,7 @@ __SERVICES_API__=${SERVICES_API:=https://mempool.space/api/v1/services}
|
|||||||
__PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false}
|
__PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false}
|
||||||
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
|
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
|
||||||
__ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
|
__ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
|
||||||
|
__STRATUM_ENABLED__=${STRATUM_ENABLED:=false}
|
||||||
|
|
||||||
# Export as environment variables to be used by envsubst
|
# Export as environment variables to be used by envsubst
|
||||||
export __MAINNET_ENABLED__
|
export __MAINNET_ENABLED__
|
||||||
@ -76,6 +77,7 @@ export __SERVICES_API__
|
|||||||
export __PUBLIC_ACCELERATIONS__
|
export __PUBLIC_ACCELERATIONS__
|
||||||
export __HISTORICAL_PRICE__
|
export __HISTORICAL_PRICE__
|
||||||
export __ADDITIONAL_CURRENCIES__
|
export __ADDITIONAL_CURRENCIES__
|
||||||
|
export __STRATUM_ENABLED__
|
||||||
|
|
||||||
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
|
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
|
||||||
echo ${folder}
|
echo ${folder}
|
||||||
|
@ -13,8 +13,8 @@ localhostIP="127.0.0.1"
|
|||||||
cp ./docker/frontend/* ./frontend
|
cp ./docker/frontend/* ./frontend
|
||||||
cp ./nginx.conf ./frontend/
|
cp ./nginx.conf ./frontend/
|
||||||
cp ./nginx-mempool.conf ./frontend/
|
cp ./nginx-mempool.conf ./frontend/
|
||||||
sed -i"" -e "s/${localhostIP}:80/0.0.0.0:__MEMPOOL_FRONTEND_HTTP_PORT__/g" ./frontend/nginx.conf
|
# sed -i"" -e "s/${localhostIP}:80/0.0.0.0:__MEMPOOL_FRONTEND_HTTP_PORT__/g" ./frontend/nginx.conf
|
||||||
sed -i"" -e "s/${localhostIP}/0.0.0.0/g" ./frontend/nginx.conf
|
# sed -i"" -e "s/${localhostIP}/0.0.0.0/g" ./frontend/nginx.conf
|
||||||
sed -i"" -e "s/user nobody;//g" ./frontend/nginx.conf
|
sed -i"" -e "s/user nobody;//g" ./frontend/nginx.conf
|
||||||
sed -i"" -e "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" ./frontend/nginx.conf
|
sed -i"" -e "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" ./frontend/nginx.conf
|
||||||
sed -i"" -e "s/${localhostIP}:8999/__MEMPOOL_BACKEND_MAINNET_HTTP_HOST__:__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__/g" ./frontend/nginx-mempool.conf
|
sed -i"" -e "s/${localhostIP}:8999/__MEMPOOL_BACKEND_MAINNET_HTTP_HOST__:__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__/g" ./frontend/nginx-mempool.conf
|
||||||
|
@ -344,7 +344,9 @@ describe('Mainnet', () => {
|
|||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
|
|
||||||
cy.changeNetwork('testnet4');
|
//TODO(knorrium): add a check for the proxied server
|
||||||
|
// cy.changeNetwork('testnet4');
|
||||||
|
|
||||||
cy.changeNetwork('signet');
|
cy.changeNetwork('signet');
|
||||||
cy.changeNetwork('mainnet');
|
cy.changeNetwork('mainnet');
|
||||||
});
|
});
|
||||||
|
@ -27,5 +27,6 @@
|
|||||||
"ACCELERATOR": false,
|
"ACCELERATOR": false,
|
||||||
"ACCELERATOR_BUTTON": true,
|
"ACCELERATOR_BUTTON": true,
|
||||||
"PUBLIC_ACCELERATIONS": false,
|
"PUBLIC_ACCELERATIONS": false,
|
||||||
|
"STRATUM_ENABLED": false,
|
||||||
"SERVICES_API": "https://mempool.space/api/v1/services"
|
"SERVICES_API": "https://mempool.space/api/v1/services"
|
||||||
}
|
}
|
||||||
|
30
frontend/package-lock.json
generated
30
frontend/package-lock.json
generated
@ -33,7 +33,7 @@
|
|||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.6.0",
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.24.0",
|
||||||
"ngx-echarts": "~17.2.0",
|
"ngx-echarts": "~17.2.0",
|
||||||
"ngx-infinite-scroll": "^17.0.0",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
@ -8724,12 +8724,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/echarts": {
|
"node_modules/echarts": {
|
||||||
"version": "5.5.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||||
"integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
|
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.3.0",
|
"tslib": "2.3.0",
|
||||||
"zrender": "5.5.0"
|
"zrender": "5.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/echarts/node_modules/tslib": {
|
"node_modules/echarts/node_modules/tslib": {
|
||||||
@ -18366,9 +18366,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zrender": {
|
"node_modules/zrender": {
|
||||||
"version": "5.5.0",
|
"version": "5.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
|
||||||
"integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
|
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.3.0"
|
"tslib": "2.3.0"
|
||||||
}
|
}
|
||||||
@ -24485,12 +24485,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"echarts": {
|
"echarts": {
|
||||||
"version": "5.5.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||||
"integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
|
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "2.3.0",
|
"tslib": "2.3.0",
|
||||||
"zrender": "5.5.0"
|
"zrender": "5.6.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": {
|
"tslib": {
|
||||||
@ -31485,9 +31485,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zrender": {
|
"zrender": {
|
||||||
"version": "5.5.0",
|
"version": "5.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
|
||||||
"integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
|
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "2.3.0"
|
"tslib": "2.3.0"
|
||||||
},
|
},
|
||||||
|
@ -86,7 +86,7 @@
|
|||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.6.0",
|
||||||
"ngx-echarts": "~17.2.0",
|
"ngx-echarts": "~17.2.0",
|
||||||
"ngx-infinite-scroll": "^17.0.0",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
"qrcode": "1.5.1",
|
"qrcode": "1.5.1",
|
||||||
|
@ -3,8 +3,10 @@ const fs = require('fs');
|
|||||||
let PROXY_CONFIG = require('./proxy.conf');
|
let PROXY_CONFIG = require('./proxy.conf');
|
||||||
|
|
||||||
PROXY_CONFIG.forEach(entry => {
|
PROXY_CONFIG.forEach(entry => {
|
||||||
entry.target = entry.target.replace("mempool.space", "mempool-staging.fra.mempool.space");
|
const hostname = process.env.CYPRESS_REROUTE_TESTNET === 'true' ? 'mempool-staging.fra.mempool.space' : 'node201.fmt.mempool.space';
|
||||||
entry.target = entry.target.replace("liquid.network", "liquid-staging.fra.mempool.space");
|
console.log(`e2e tests running against ${hostname}`);
|
||||||
|
entry.target = entry.target.replace("mempool.space", hostname);
|
||||||
|
entry.target = entry.target.replace("liquid.network", "liquid-staging.fmt.mempool.space");
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = PROXY_CONFIG;
|
module.exports = PROXY_CONFIG;
|
||||||
|
@ -217,7 +217,7 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<ng-template ngFor let-sponsor [ngForOf]="profiles.whales">
|
<ng-template ngFor let-sponsor [ngForOf]="profiles.whales">
|
||||||
<a [href]="'https://x.com/' + sponsor.username" target="_blank" rel="sponsored" [title]="sponsor.username">
|
<a [href]="'https://x.com/' + sponsor.username" target="_blank" rel="sponsored" [title]="sponsor.username">
|
||||||
<img class="image" [src]="'/api/v1/services/account/images/' + sponsor.username + '/md5=' + sponsor.imageMd5" onError="this.src = '/resources/profile/grumpy.svg'; this.className = 'image unknown'"/>
|
<img class="image" [src]="'/api/v1/services/account/images/' + sponsor.username" onError="this.src = '/resources/profile/grumpy.svg'; this.className = 'image unknown'"/>
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -229,7 +229,7 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<ng-template ngFor let-sponsor [ngForOf]="profiles.chads">
|
<ng-template ngFor let-sponsor [ngForOf]="profiles.chads">
|
||||||
<a [href]="'https://x.com/' + sponsor.username" target="_blank" rel="sponsored" [title]="sponsor.username">
|
<a [href]="'https://x.com/' + sponsor.username" target="_blank" rel="sponsored" [title]="sponsor.username">
|
||||||
<img class="image" [src]="'/api/v1/services/account/images/' + sponsor.username + '/md5=' + sponsor.imageMd5" onError="this.src = '/resources/profile/grumpy.svg'; this.className = 'image unknown'"/>
|
<img class="image" [src]="'/api/v1/services/account/images/' + sponsor.username" onError="this.src = '/resources/profile/grumpy.svg'; this.className = 'image unknown'"/>
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
<div class="box card w-100" style="background: var(--box-bg)" id=acceleratePreviewAnchor>
|
<div class="box card w-100 accelerate-checkout-inner" [class.input-disabled]="isCheckoutLocked > 0" style="background: var(--box-bg)" id=acceleratePreviewAnchor>
|
||||||
@if (accelerateError) {
|
@if (accelerateError) {
|
||||||
<div class="row mb-1 text-center">
|
@if (accelerateError.includes('Payment declined')) {
|
||||||
<div class="col-sm">
|
<div class="row mb-1 text-center">
|
||||||
<h1 style="font-size: larger;" i18n="accelerator.sorry-error-title">Sorry, something went wrong!</h1>
|
<div class="col-sm">
|
||||||
|
<h1 style="font-size: larger;">{{ accelerateError }}</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
} @else {
|
||||||
|
<div class="row mb-1 text-center">
|
||||||
|
<div class="col-sm">
|
||||||
|
<h1 style="font-size: larger;" i18n="accelerator.sorry-error-title">Sorry, something went wrong!</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="row text-center mt-1">
|
<div class="row text-center mt-1">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="d-flex flex-row justify-content-center align-items-center">
|
<div class="d-flex flex-row justify-content-center align-items-center">
|
||||||
@ -357,11 +365,11 @@
|
|||||||
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true" class="ml-2"></app-active-acceleration-box>
|
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true" class="ml-2"></app-active-acceleration-box>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="payment-area mt-2 p-2" style="font-size: 14px;">
|
<div class="payment-area" style="font-size: 14px;">
|
||||||
<div class="row text-center justify-content-center mx-2">
|
<div class="row text-center justify-content-center mx-2">
|
||||||
<p i18n="accelerator.payment-to-mempool-space">Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + tx.txid" target="_blank">{{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}</a></p>
|
<span i18n="accelerator.payment-to-mempool-space">Payment to mempool.space for acceleration of txid <a [routerLink]="'/tx/' + tx.txid" target="_blank">{{ tx.txid.substr(0, 10) }}..{{ tx.txid.substr(-10) }}</a></span>
|
||||||
</div>
|
</div>
|
||||||
@if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp)) {
|
@if (canPayWithBalance || !(canPayWithBitcoin || canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay)) {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
|
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
|
||||||
<p><ng-container i18n="accelerator.your-account-will-be-debited">Your account will be debited no more than</ng-container> <small style="font-family: monospace;">{{ cost | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></p>
|
<p><ng-container i18n="accelerator.your-account-will-be-debited">Your account will be debited no more than</ng-container> <small style="font-family: monospace;">{{ cost | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></p>
|
||||||
@ -378,9 +386,12 @@
|
|||||||
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <span><small style="font-family: monospace;">{{ ((invoice.btcDue * 100_000_000) || cost) | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></span></p>
|
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <span><small style="font-family: monospace;">{{ ((invoice.btcDue * 100_000_000) || cost) | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></span></p>
|
||||||
<app-bitcoin-invoice style="width: 100%;" [invoice]="invoice" [minimal]="true" (completed)="bitcoinPaymentCompleted()"></app-bitcoin-invoice>
|
<app-bitcoin-invoice style="width: 100%;" [invoice]="invoice" [minimal]="true" (completed)="bitcoinPaymentCompleted()"></app-bitcoin-invoice>
|
||||||
} @else if (btcpayInvoiceFailed) {
|
} @else if (btcpayInvoiceFailed) {
|
||||||
<p i18n="accelerator.failed-to-load-invoice">Failed to load invoice</p>
|
<div class="btcpay-invoice">
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center" style="width: 100%; height: 292px;">
|
<fa-icon style="font-size: 20px; color: var(--red)" [icon]="['fas', 'circle-xmark']"></fa-icon>
|
||||||
<fa-icon style="font-size: 24px; color: var(--red)" [icon]="['fas', 'circle-xmark']"></fa-icon>
|
<span i18n="accelerator.failed-to-load-invoice">Failed to load invoice</span>
|
||||||
|
@if (!loadingBtcpayInvoice) {
|
||||||
|
<button class="btn btn-sm btn-secondary mt-0 mt-md-1" (click)="requestBTCPayInvoice()">Retry ↻</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<p i18n="accelerator.loading-invoice">Loading invoice...</p>
|
<p i18n="accelerator.loading-invoice">Loading invoice...</p>
|
||||||
@ -389,13 +400,13 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) {
|
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) {
|
||||||
<div class="col-sm text-center flex-grow-0 d-flex flex-column justify-content-center align-items-center">
|
<div class="col-sm text-center flex-grow-0 d-flex flex-column justify-content-center align-items-center">
|
||||||
<p class="text-nowrap">—<span i18n="or">OR</span>—</p>
|
<p class="text-nowrap">——<span i18n="or"> OR </span>——</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) {
|
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) {
|
||||||
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
|
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
|
||||||
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <app-fiat [value]="cost"></app-fiat> with</p>
|
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <app-fiat [value]="cost"></app-fiat> with</p>
|
||||||
@if (canPayWithCashapp) {
|
@if (canPayWithCashapp) {
|
||||||
@ -413,6 +424,17 @@
|
|||||||
<img src="/resources/google-pay.png" height=37>
|
<img src="/resources/google-pay.png" height=37>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@if (canPayWithCardOnFile) {
|
||||||
|
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { <span class="mt-1 mb-1"></span> }
|
||||||
|
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px" (click)="moveToStep('cardonfile')">
|
||||||
|
@if (['VISA', 'MASTERCARD', 'JCB', 'DISCOVER', 'DISCOVER_DINERS', 'AMERICAN_EXPRESS'].includes(estimate?.availablePaymentMethods?.cardOnFile?.card?.brand)) {
|
||||||
|
<app-svg-images [name]="estimate?.availablePaymentMethods?.cardOnFile?.card?.brand" height="33" class="mr-2"></app-svg-images>
|
||||||
|
} @else {
|
||||||
|
<app-svg-images name="OTHER_BRAND" height="33" class="mr-2"></app-svg-images>
|
||||||
|
}
|
||||||
|
<span style="font-size: 22px; padding-bottom: 3px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -435,7 +457,7 @@
|
|||||||
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('summary')" i18n="go-back">Go back</button>
|
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('summary')" i18n="go-back">Go back</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @else if (step === 'cashapp' || step === 'applepay' || step === 'googlepay') {
|
} @else if (step === 'cashapp' || step === 'applepay' || step === 'googlepay' || step === 'cardonfile') {
|
||||||
<!-- Show checkout page -->
|
<!-- Show checkout page -->
|
||||||
<div class="row mb-md-1 text-center" id="confirm-title">
|
<div class="row mb-md-1 text-center" id="confirm-title">
|
||||||
<div class="col-sm" id="confirm-payment-title">
|
<div class="col-sm" id="confirm-payment-title">
|
||||||
@ -451,7 +473,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay || step === 'googlepay' && !loadingGooglePay) {
|
@if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay || step === 'googlepay' && !loadingGooglePay || step === 'cardonfile' && !loadingCardOnFile) {
|
||||||
<div class="row text-center mt-1">
|
<div class="row text-center mt-1">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="form-group w-100">
|
<div class="form-group w-100">
|
||||||
@ -476,14 +498,24 @@
|
|||||||
<div id="cash-app-pay" class="d-inline-block" style="height: 50px" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
<div id="cash-app-pay" class="d-inline-block" style="height: 50px" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
||||||
} @else if (step === 'googlepay') {
|
} @else if (step === 'googlepay') {
|
||||||
<div id="google-pay-button" class="d-inline-block" style="height: 50px" [style]="loadingGooglePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
<div id="google-pay-button" class="d-inline-block" style="height: 50px" [style]="loadingGooglePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
||||||
|
} @else if (step === 'cardonfile') {
|
||||||
|
<div class="paymentMethod mx-2 d-flex justify-content-center align-items-center ml-auto mr-auto" style="width: 200px; height: 55px" (click)="requestCardOnFilePayment()" [style]="loadingCardOnFile ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''">
|
||||||
|
<fa-icon style="font-size: 24px; color: white" [icon]="['fas', 'credit-card']"></fa-icon>
|
||||||
|
<span class="ml-2" style="font-size: 22px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.brand }} {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@if (loadingCashapp || loadingApplePay || loadingGooglePay) {
|
@if (loadingCashapp || loadingApplePay || loadingGooglePay || loadingCardOnFile) {
|
||||||
<div display="d-flex flex-row justify-content-center">
|
<div display="d-flex flex-row justify-content-center">
|
||||||
<span i18n="accelerator.loading-payment-method">Loading payment method...</span>
|
<span i18n="accelerator.loading-payment-method">Loading payment method...</span>
|
||||||
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@if (isTokenizing > 0) {
|
||||||
|
<div class="d-flex flex-row justify-content-center">
|
||||||
|
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -8,6 +8,13 @@
|
|||||||
color: var(--green)
|
color: var(--green)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accelerate-checkout-inner {
|
||||||
|
&.input-disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.paymentMethod {
|
.paymentMethod {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
@ -146,6 +153,11 @@
|
|||||||
|
|
||||||
.payment-area {
|
.payment-area {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
@media (max-width: 575px) {
|
||||||
|
padding-bottom: 1.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.col.pie {
|
.col.pie {
|
||||||
@ -212,4 +224,17 @@
|
|||||||
}
|
}
|
||||||
.apple-pay-button-white-with-line {
|
.apple-pay-button-white-with-line {
|
||||||
-apple-pay-button-style: white-outline;
|
-apple-pay-button-style: white-outline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btcpay-invoice {
|
||||||
|
display: flex;
|
||||||
|
height: 292px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
@media (max-width: 575px) {
|
||||||
|
height: 75px;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ import { EnterpriseService } from '@app/services/enterprise.service';
|
|||||||
import { ApiService } from '@app/services/api.service';
|
import { ApiService } from '@app/services/api.service';
|
||||||
import { isDevMode } from '@angular/core';
|
import { isDevMode } from '@angular/core';
|
||||||
|
|
||||||
export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay';
|
export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay' | 'cardOnFile';
|
||||||
|
|
||||||
export type AccelerationEstimate = {
|
export type AccelerationEstimate = {
|
||||||
hasAccess: boolean;
|
hasAccess: boolean;
|
||||||
@ -26,7 +26,7 @@ export type AccelerationEstimate = {
|
|||||||
mempoolBaseFee: number;
|
mempoolBaseFee: number;
|
||||||
vsizeFee: number;
|
vsizeFee: number;
|
||||||
pools: number[];
|
pools: number[];
|
||||||
availablePaymentMethods: Record<PaymentMethod, {min: number, max: number}>;
|
availablePaymentMethods: Record<PaymentMethod, {min: number, max: number, card?: {card_id: string, last_4: string, brand: string, name: string, billing: any}}>;
|
||||||
unavailable?: boolean;
|
unavailable?: boolean;
|
||||||
options: { // recommended bid options
|
options: { // recommended bid options
|
||||||
fee: number; // recommended userBid in sats
|
fee: number; // recommended userBid in sats
|
||||||
@ -49,7 +49,7 @@ export const MIN_BID_RATIO = 1;
|
|||||||
export const DEFAULT_BID_RATIO = 2;
|
export const DEFAULT_BID_RATIO = 2;
|
||||||
export const MAX_BID_RATIO = 4;
|
export const MAX_BID_RATIO = 4;
|
||||||
|
|
||||||
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'processing' | 'paid' | 'success';
|
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'cardonfile' | 'processing' | 'paid' | 'success';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-accelerate-checkout',
|
selector: 'app-accelerate-checkout',
|
||||||
@ -62,9 +62,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
@Input() miningStats: MiningStats;
|
@Input() miningStats: MiningStats;
|
||||||
@Input() eta: ETA;
|
@Input() eta: ETA;
|
||||||
@Input() scrollEvent: boolean;
|
@Input() scrollEvent: boolean;
|
||||||
@Input() cashappEnabled: boolean = true;
|
|
||||||
@Input() applePayEnabled: boolean = false;
|
@Input() applePayEnabled: boolean = false;
|
||||||
@Input() googlePayEnabled: boolean = true;
|
@Input() googlePayEnabled: boolean = true;
|
||||||
|
@Input() cardOnFileEnabled: boolean = true;
|
||||||
@Input() advancedEnabled: boolean = false;
|
@Input() advancedEnabled: boolean = false;
|
||||||
@Input() forceMobile: boolean = false;
|
@Input() forceMobile: boolean = false;
|
||||||
@Input() showDetails: boolean = false;
|
@Input() showDetails: boolean = false;
|
||||||
@ -76,6 +76,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
calculating = true;
|
calculating = true;
|
||||||
processing = false;
|
processing = false;
|
||||||
|
isCheckoutLocked = 0; // reference counter, 0 = unlocked, >0 = locked
|
||||||
|
isTokenizing = 0; // reference counter, 0 = false, >0 = true
|
||||||
selectedOption: 'wait' | 'accel';
|
selectedOption: 'wait' | 'accel';
|
||||||
cantPayReason = '';
|
cantPayReason = '';
|
||||||
quoteError = ''; // error fetching estimate or initial data
|
quoteError = ''; // error fetching estimate or initial data
|
||||||
@ -115,6 +117,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
loadingCashapp = false;
|
loadingCashapp = false;
|
||||||
loadingApplePay = false;
|
loadingApplePay = false;
|
||||||
loadingGooglePay = false;
|
loadingGooglePay = false;
|
||||||
|
loadingCardOnFile = false;
|
||||||
payments: any;
|
payments: any;
|
||||||
cashAppPay: any;
|
cashAppPay: any;
|
||||||
applePay: any;
|
applePay: any;
|
||||||
@ -154,7 +157,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
this.accelerateError = null;
|
this.accelerateError = null;
|
||||||
this.timePaid = 0;
|
this.timePaid = 0;
|
||||||
this.btcpayInvoiceFailed = false;
|
this.btcpayInvoiceFailed = false;
|
||||||
this.moveToStep('summary');
|
this.moveToStep('summary', true);
|
||||||
} else {
|
} else {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
@ -163,11 +166,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
if (urlParams.get('cash_request_id')) { // Redirected from cashapp
|
if (urlParams.get('cash_request_id')) { // Redirected from cashapp
|
||||||
this.moveToStep('processing');
|
this.moveToStep('processing', true);
|
||||||
this.insertSquare();
|
this.insertSquare();
|
||||||
this.setupSquare();
|
this.setupSquare();
|
||||||
} else {
|
} else {
|
||||||
this.moveToStep('summary');
|
this.moveToStep('summary', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||||
@ -192,20 +195,23 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
if (changes.accelerating && this.accelerating) {
|
if (changes.accelerating && this.accelerating) {
|
||||||
if (this.step === 'processing' || this.step === 'paid') {
|
if (this.step === 'processing' || this.step === 'paid') {
|
||||||
this.moveToStep('success');
|
this.moveToStep('success', true);
|
||||||
} else { // Edge case where the transaction gets accelerated by someone else or on another session
|
} else { // Edge case where the transaction gets accelerated by someone else or on another session
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToStep(step: CheckoutStep): void {
|
moveToStep(step: CheckoutStep, force: boolean = false): void {
|
||||||
|
if (this.isCheckoutLocked > 0 && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this._step = step;
|
this._step = step;
|
||||||
if (this.timeoutTimer) {
|
if (this.timeoutTimer) {
|
||||||
clearTimeout(this.timeoutTimer);
|
clearTimeout(this.timeoutTimer);
|
||||||
}
|
}
|
||||||
if (!this.estimate && ['quote', 'summary', 'checkout'].includes(this.step)) {
|
if (!this.estimate && ['quote', 'summary', 'checkout', 'processing'].includes(this.step)) {
|
||||||
this.fetchEstimate();
|
this.fetchEstimate();
|
||||||
}
|
}
|
||||||
if (this._step === 'checkout') {
|
if (this._step === 'checkout') {
|
||||||
@ -214,10 +220,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
if (this._step === 'checkout' && this.canPayWithBitcoin) {
|
if (this._step === 'checkout' && this.canPayWithBitcoin) {
|
||||||
this.btcpayInvoiceFailed = false;
|
this.btcpayInvoiceFailed = false;
|
||||||
this.loadingBtcpayInvoice = true;
|
|
||||||
this.invoice = null;
|
this.invoice = null;
|
||||||
this.requestBTCPayInvoice();
|
this.requestBTCPayInvoice();
|
||||||
} else if (this._step === 'cashapp' && this.cashappEnabled) {
|
} else if (this._step === 'cashapp') {
|
||||||
this.loadingCashapp = true;
|
this.loadingCashapp = true;
|
||||||
this.setupSquare();
|
this.setupSquare();
|
||||||
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
|
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
|
||||||
@ -229,6 +234,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
this.loadingGooglePay = true;
|
this.loadingGooglePay = true;
|
||||||
this.setupSquare();
|
this.setupSquare();
|
||||||
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
|
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
|
||||||
|
} else if (this._step === 'cardonfile' && this.cardOnFileEnabled) {
|
||||||
|
this.loadingCardOnFile = true;
|
||||||
|
this.setupSquare();
|
||||||
|
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
|
||||||
} else if (this._step === 'paid') {
|
} else if (this._step === 'paid') {
|
||||||
this.timePaid = Date.now();
|
this.timePaid = Date.now();
|
||||||
this.timeoutTimer = setTimeout(() => {
|
this.timeoutTimer = setTimeout(() => {
|
||||||
@ -242,7 +251,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
closeModal(): void {
|
closeModal(): void {
|
||||||
this.completed.emit(true);
|
this.completed.emit(true);
|
||||||
this.moveToStep('summary');
|
this.moveToStep('summary', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -323,7 +332,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.step === 'checkout' && this.canPayWithBitcoin && !this.loadingBtcpayInvoice) {
|
if (this.step === 'checkout' && this.canPayWithBitcoin && !this.loadingBtcpayInvoice) {
|
||||||
this.loadingBtcpayInvoice = true;
|
|
||||||
this.requestBTCPayInvoice();
|
this.requestBTCPayInvoice();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +401,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
this.audioService.playSound('ascend-chime-cartoon');
|
this.audioService.playSound('ascend-chime-cartoon');
|
||||||
this.showSuccess = true;
|
this.showSuccess = true;
|
||||||
this.estimateSubscription.unsubscribe();
|
this.estimateSubscription.unsubscribe();
|
||||||
this.moveToStep('paid');
|
this.moveToStep('paid', true);
|
||||||
},
|
},
|
||||||
error: (response) => {
|
error: (response) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
@ -449,6 +457,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
await this.requestApplePayPayment();
|
await this.requestApplePayPayment();
|
||||||
} else if (this._step === 'googlepay') {
|
} else if (this._step === 'googlepay') {
|
||||||
await this.requestGooglePayPayment();
|
await this.requestGooglePayPayment();
|
||||||
|
} else if (this._step === 'cardonfile') {
|
||||||
|
this.loadingCardOnFile = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
@ -503,56 +513,75 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.loadingApplePay = false;
|
this.loadingApplePay = false;
|
||||||
applePayButton.addEventListener('click', async event => {
|
applePayButton.addEventListener('click', async event => {
|
||||||
|
if (this.isCheckoutLocked > 0 || this.isTokenizing > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const tokenResult = await this.applePay.tokenize();
|
try {
|
||||||
if (tokenResult?.status === 'OK') {
|
// lock the checkout UI and show a loading spinner until the square modals are finished
|
||||||
const card = tokenResult.details?.card;
|
this.isCheckoutLocked++;
|
||||||
if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
|
this.isTokenizing++;
|
||||||
console.error(`Cannot retreive payment card details`);
|
const tokenResult = await this.applePay.tokenize();
|
||||||
this.accelerateError = 'apple_pay_no_card_details';
|
if (tokenResult?.status === 'OK') {
|
||||||
this.processing = false;
|
const card = tokenResult.details?.card;
|
||||||
return;
|
if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
|
||||||
}
|
console.error(`Cannot retreive payment card details`);
|
||||||
const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase());
|
this.accelerateError = 'apple_pay_no_card_details';
|
||||||
this.servicesApiService.accelerateWithApplePay$(
|
|
||||||
this.tx.txid,
|
|
||||||
tokenResult.token,
|
|
||||||
cardTag,
|
|
||||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
|
||||||
costUSD
|
|
||||||
).subscribe({
|
|
||||||
next: () => {
|
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
return;
|
||||||
this.audioService.playSound('ascend-chime-cartoon');
|
|
||||||
if (this.applePay) {
|
|
||||||
this.applePay.destroy();
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.moveToStep('paid');
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
error: (response) => {
|
|
||||||
this.processing = false;
|
|
||||||
this.accelerateError = response.error;
|
|
||||||
if (!(response.status === 403 && response.error === 'not_available')) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// Reset everything by reloading the page :D, can be improved
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase());
|
||||||
} else {
|
// keep checkout in loading state until the acceleration request completes
|
||||||
this.processing = false;
|
this.isTokenizing++;
|
||||||
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
this.isCheckoutLocked++;
|
||||||
if (tokenResult.errors) {
|
this.servicesApiService.accelerateWithApplePay$(
|
||||||
errorMessage += ` and errors: ${JSON.stringify(
|
this.tx.txid,
|
||||||
tokenResult.errors,
|
tokenResult.token,
|
||||||
)}`;
|
cardTag,
|
||||||
|
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
|
costUSD
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.processing = false;
|
||||||
|
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||||
|
this.audioService.playSound('ascend-chime-cartoon');
|
||||||
|
if (this.applePay) {
|
||||||
|
this.applePay.destroy();
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
this.moveToStep('paid', true);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
error: (response) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.accelerateError = response.error;
|
||||||
|
if (!(response.status === 403 && response.error === 'not_available')) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
// Reset everything by reloading the page :D, can be improved
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.processing = false;
|
||||||
|
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
||||||
|
if (tokenResult.errors) {
|
||||||
|
errorMessage += ` and errors: ${JSON.stringify(
|
||||||
|
tokenResult.errors,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
throw new Error(errorMessage);
|
} finally {
|
||||||
|
// always unlock the checkout once we're finished
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.isCheckoutLocked--;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -602,70 +631,193 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
this.loadingGooglePay = false;
|
this.loadingGooglePay = false;
|
||||||
|
|
||||||
document.getElementById('google-pay-button').addEventListener('click', async event => {
|
document.getElementById('google-pay-button').addEventListener('click', async event => {
|
||||||
|
if (this.isCheckoutLocked > 0 || this.isTokenizing > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const tokenResult = await this.googlePay.tokenize();
|
try {
|
||||||
if (tokenResult?.status === 'OK') {
|
// lock the checkout UI and show a loading spinner until the square modals are finished
|
||||||
const card = tokenResult.details?.card;
|
this.isCheckoutLocked++;
|
||||||
if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
|
this.isTokenizing++;
|
||||||
console.error(`Cannot retreive payment card details`);
|
const tokenResult = await this.googlePay.tokenize();
|
||||||
this.accelerateError = 'apple_pay_no_card_details';
|
if (tokenResult?.status === 'OK') {
|
||||||
this.processing = false;
|
const card = tokenResult.details?.card;
|
||||||
return;
|
if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
|
||||||
}
|
console.error(`Cannot retreive payment card details`);
|
||||||
const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2));
|
this.accelerateError = 'apple_pay_no_card_details';
|
||||||
if (!verificationToken) {
|
|
||||||
console.error(`SCA verification failed`);
|
|
||||||
this.accelerateError = 'SCA Verification Failed. Payment Declined.';
|
|
||||||
this.processing = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase());
|
|
||||||
this.servicesApiService.accelerateWithGooglePay$(
|
|
||||||
this.tx.txid,
|
|
||||||
tokenResult.token,
|
|
||||||
verificationToken,
|
|
||||||
cardTag,
|
|
||||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
|
||||||
costUSD
|
|
||||||
).subscribe({
|
|
||||||
next: () => {
|
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
return;
|
||||||
this.audioService.playSound('ascend-chime-cartoon');
|
|
||||||
if (this.googlePay) {
|
|
||||||
this.googlePay.destroy();
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.moveToStep('paid');
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
error: (response) => {
|
|
||||||
this.processing = false;
|
|
||||||
this.accelerateError = response.error;
|
|
||||||
if (!(response.status === 403 && response.error === 'not_available')) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// Reset everything by reloading the page :D, can be improved
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2));
|
||||||
} else {
|
if (!verificationToken || !verificationToken.token) {
|
||||||
this.processing = false;
|
console.error(`SCA verification failed`);
|
||||||
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
this.accelerateError = 'SCA Verification Failed. Payment Declined.';
|
||||||
if (tokenResult.errors) {
|
this.processing = false;
|
||||||
errorMessage += ` and errors: ${JSON.stringify(
|
return;
|
||||||
tokenResult.errors,
|
}
|
||||||
)}`;
|
const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase());
|
||||||
|
// keep checkout in loading state until the acceleration request completes
|
||||||
|
this.isCheckoutLocked++;
|
||||||
|
this.isTokenizing++;
|
||||||
|
this.servicesApiService.accelerateWithGooglePay$(
|
||||||
|
this.tx.txid,
|
||||||
|
tokenResult.token,
|
||||||
|
verificationToken.token,
|
||||||
|
cardTag,
|
||||||
|
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
|
costUSD,
|
||||||
|
verificationToken.userChallenged
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.processing = false;
|
||||||
|
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||||
|
this.audioService.playSound('ascend-chime-cartoon');
|
||||||
|
if (this.googlePay) {
|
||||||
|
this.googlePay.destroy();
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
this.moveToStep('paid', true);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
error: (response) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.accelerateError = response.error;
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
if (!(response.status === 403 && response.error === 'not_available')) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Reset everything by reloading the page :D, can be improved
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.processing = false;
|
||||||
|
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
||||||
|
if (tokenResult.errors) {
|
||||||
|
errorMessage += ` and errors: ${JSON.stringify(
|
||||||
|
tokenResult.errors,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
throw new Error(errorMessage);
|
} finally {
|
||||||
|
// always unlock the checkout once we're finished
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.isCheckoutLocked--;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Card On File
|
||||||
|
*/
|
||||||
|
async requestCardOnFilePayment(): Promise<void> {
|
||||||
|
if (this.processing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.conversionsSubscription) {
|
||||||
|
this.conversionsSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.processing = true;
|
||||||
|
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||||
|
async (conversions) => {
|
||||||
|
this.conversions = conversions;
|
||||||
|
|
||||||
|
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
||||||
|
if (this.isCheckoutLocked > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cardOnFile = this.estimate?.availablePaymentMethods?.cardOnFile;
|
||||||
|
if (!cardOnFile?.card) {
|
||||||
|
this.accelerateError = 'card_on_file_not_found';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadingCardOnFile = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.isCheckoutLocked += 2;
|
||||||
|
this.isTokenizing += 2;
|
||||||
|
|
||||||
|
const nameParts = cardOnFile.card.name.split(' ');
|
||||||
|
const assumedGivenName = nameParts[0];
|
||||||
|
const assumedFamilyName = nameParts.length > 1 ? nameParts[1] : undefined;
|
||||||
|
const verificationDetails = {
|
||||||
|
card: {
|
||||||
|
billing: {
|
||||||
|
givenName: assumedGivenName,
|
||||||
|
familyName: assumedFamilyName,
|
||||||
|
addressLines: [cardOnFile.card.billing.addressLine1 ?? ''],
|
||||||
|
city: cardOnFile.card.billing.locality ?? '',
|
||||||
|
state: cardOnFile.card.billing.administrativeDistrictLevel1 ?? '',
|
||||||
|
countyCode: cardOnFile.card.billing.country,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const verificationToken = await this.$verifyBuyer(this.payments, cardOnFile.card.card_id, verificationDetails, costUSD.toFixed(2));
|
||||||
|
if (!verificationToken || !verificationToken.token) {
|
||||||
|
console.error(`SCA verification failed`);
|
||||||
|
this.accelerateError = 'SCA Verification Failed. Payment Declined.';
|
||||||
|
this.processing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.servicesApiService.accelerateWithCardOnFile$(
|
||||||
|
this.tx.txid,
|
||||||
|
cardOnFile.card.card_id,
|
||||||
|
verificationToken.token,
|
||||||
|
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
|
costUSD,
|
||||||
|
verificationToken.userChallenged
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.processing = false;
|
||||||
|
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||||
|
this.audioService.playSound('ascend-chime-cartoon');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.moveToStep('paid', true);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
error: (response) => {
|
||||||
|
this.processing = false;
|
||||||
|
this.accelerateError = response.error;
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
this.isTokenizing--;
|
||||||
|
if (!(response.status === 403 && response.error === 'not_available')) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Reset everything by reloading the page :D, can be improved
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
this.isTokenizing--;
|
||||||
|
this.processing = false;
|
||||||
|
this.accelerateError = e.message;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// always unlock the checkout once we're finished
|
||||||
|
this.isCheckoutLocked--;
|
||||||
|
this.isTokenizing--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CASHAPP
|
* CASHAPP
|
||||||
*/
|
*/
|
||||||
@ -686,7 +838,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`;
|
const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`;
|
||||||
const costUSD = this.step === 'processing' ? 69.69 : (this.cost / 100_000_000 * conversions.USD); // When we're redirected to this component, the payment data is already linked to the payment token, so does not matter what amonut we put in there, therefore it's 69.69
|
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
||||||
const paymentRequest = this.payments.paymentRequest({
|
const paymentRequest = this.payments.paymentRequest({
|
||||||
countryCode: 'US',
|
countryCode: 'US',
|
||||||
currencyCode: 'USD',
|
currencyCode: 'USD',
|
||||||
@ -726,7 +878,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
this.cashAppPay.destroy();
|
this.cashAppPay.destroy();
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.moveToStep('paid');
|
this.moveToStep('paid', true);
|
||||||
if (window.history.replaceState) {
|
if (window.history.replaceState) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
window.history.replaceState(null, null, window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ''));
|
window.history.replaceState(null, null, window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ''));
|
||||||
@ -741,7 +893,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
// Reset everything by reloading the page :D, can be improved
|
// Reset everything by reloading the page :D, can be improved
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
||||||
}, 3000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -752,9 +904,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required in SCA Mandated Regions: Learn more at https://developer.squareup.com/docs/sca-overview
|
* https://developer.squareup.com/docs/sca-overview
|
||||||
*/
|
*/
|
||||||
async $verifyBuyer(payments, token, details, amount) {
|
async $verifyBuyer(payments, token, details, amount): Promise<{token: string, userChallenged: boolean}> {
|
||||||
const verificationDetails = {
|
const verificationDetails = {
|
||||||
amount: amount,
|
amount: amount,
|
||||||
currencyCode: 'USD',
|
currencyCode: 'USD',
|
||||||
@ -774,23 +926,26 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
token,
|
token,
|
||||||
verificationDetails,
|
verificationDetails,
|
||||||
);
|
);
|
||||||
return verificationResults.token;
|
return verificationResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BTCPay
|
* BTCPay
|
||||||
*/
|
*/
|
||||||
async requestBTCPayInvoice(): Promise<void> {
|
async requestBTCPayInvoice(): Promise<void> {
|
||||||
|
this.loadingBtcpayInvoice = true;
|
||||||
this.servicesApiService.generateBTCPayAcceleratorInvoice$(this.tx.txid, this.userBid).pipe(
|
this.servicesApiService.generateBTCPayAcceleratorInvoice$(this.tx.txid, this.userBid).pipe(
|
||||||
switchMap(response => {
|
switchMap(response => {
|
||||||
return this.servicesApiService.retreiveInvoice$(response.btcpayInvoiceId);
|
return this.servicesApiService.retreiveInvoice$(response.btcpayInvoiceId);
|
||||||
}),
|
}),
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
this.loadingBtcpayInvoice = false;
|
||||||
this.btcpayInvoiceFailed = true;
|
this.btcpayInvoiceFailed = true;
|
||||||
return of(null);
|
return of(null);
|
||||||
})
|
})
|
||||||
).subscribe((invoice) => {
|
).subscribe((invoice) => {
|
||||||
|
this.loadingBtcpayInvoice = false;
|
||||||
this.invoice = invoice;
|
this.invoice = invoice;
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
@ -800,7 +955,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||||
this.audioService.playSound('ascend-chime-cartoon');
|
this.audioService.playSound('ascend-chime-cartoon');
|
||||||
this.estimateSubscription.unsubscribe();
|
this.estimateSubscription.unsubscribe();
|
||||||
this.moveToStep('paid');
|
this.moveToStep('paid', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoggedIn(): boolean {
|
isLoggedIn(): boolean {
|
||||||
@ -827,9 +982,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get couldPayWithCashapp(): boolean {
|
get couldPayWithCashapp(): boolean {
|
||||||
if (!this.cashappEnabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !!this.estimate?.availablePaymentMethods?.cashapp;
|
return !!this.estimate?.availablePaymentMethods?.cashapp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -864,7 +1016,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get canPayWithCashapp(): boolean {
|
get canPayWithCashapp(): boolean {
|
||||||
if (!this.cashappEnabled || !this.conversions || (!this.isProdDomain && !isDevMode())) {
|
if (!this.conversions || (!this.isProdDomain && !isDevMode())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -911,6 +1063,22 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canPayWithCardOnFile(): boolean {
|
||||||
|
if (!this.cardOnFileEnabled || !this.conversions || (!this.isProdDomain && !isDevMode())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentMethod = this.estimate?.availablePaymentMethods?.cardOnFile;
|
||||||
|
if (paymentMethod) {
|
||||||
|
const costUSD = (this.cost / 100_000_000 * this.conversions.USD);
|
||||||
|
if (costUSD >= paymentMethod.min && costUSD <= paymentMethod.max) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
get canPayWithBalance(): boolean {
|
get canPayWithBalance(): boolean {
|
||||||
if (!this.hasAccessToBalanceMode) {
|
if (!this.hasAccessToBalanceMode) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -46,6 +46,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
|||||||
|
|
||||||
aggregatedHistory$: Observable<any>;
|
aggregatedHistory$: Observable<any>;
|
||||||
statsSubscription: Subscription;
|
statsSubscription: Subscription;
|
||||||
|
aggregatedHistorySubscription: Subscription;
|
||||||
|
fragmentSubscription: Subscription;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
formatNumber = formatNumber;
|
formatNumber = formatNumber;
|
||||||
timespan = '';
|
timespan = '';
|
||||||
@ -79,8 +81,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
|||||||
}
|
}
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||||
|
|
||||||
this.route.fragment.subscribe((fragment) => {
|
this.fragmentSubscription = this.route.fragment.subscribe((fragment) => {
|
||||||
if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) {
|
if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) {
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||||
}
|
}
|
||||||
@ -113,7 +115,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
|||||||
share(),
|
share(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.aggregatedHistory$.subscribe();
|
this.aggregatedHistorySubscription = this.aggregatedHistory$.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@ -335,8 +337,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.statsSubscription) {
|
this.aggregatedHistorySubscription?.unsubscribe();
|
||||||
this.statsSubscription.unsubscribe();
|
this.fragmentSubscription?.unsubscribe();
|
||||||
}
|
this.statsSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<th class="time text-right" i18n="accelerator.requested">Requested</th>
|
<th class="time text-right" i18n="accelerator.requested">Requested</th>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!pending">
|
<ng-container *ngIf="!pending">
|
||||||
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
<th class="fee text-right text-truncate" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
||||||
<th class="block text-right" i18n="shared.block-title">Block</th>
|
<th class="block text-right" i18n="shared.block-title">Block</th>
|
||||||
<th class="pool text-right" i18n="mining.pool-name" *ngIf="!this.widget">Pool</th>
|
<th class="pool text-right" i18n="mining.pool-name" *ngIf="!this.widget">Pool</th>
|
||||||
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
||||||
@ -64,7 +64,8 @@
|
|||||||
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
|
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
|
||||||
<span *ngIf="acceleration.status.includes('completed') && acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]" class="badge badge-success"><ng-container i18n="accelerator.completed">Completed</ng-container><span *ngIf="acceleration.status === 'completed_provisional'"> ⌛</span></span>
|
<span *ngIf="acceleration.status.includes('completed') && acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]" class="badge badge-success"><ng-container i18n="accelerator.completed">Completed</ng-container><span *ngIf="acceleration.status === 'completed_provisional'"> ⌛</span></span>
|
||||||
<span *ngIf="acceleration.status.includes('completed') && (!acceleration.minedByPoolUniqueId || !pools[acceleration.minedByPoolUniqueId])" class="badge badge-success"><ng-container i18n="transaction.rbf.mined">Mined</ng-container><span *ngIf="acceleration.status === 'completed_provisional'"> ⌛</span></span>
|
<span *ngIf="acceleration.status.includes('completed') && (!acceleration.minedByPoolUniqueId || !pools[acceleration.minedByPoolUniqueId])" class="badge badge-success"><ng-container i18n="transaction.rbf.mined">Mined</ng-container><span *ngIf="acceleration.status === 'completed_provisional'"> ⌛</span></span>
|
||||||
<span *ngIf="acceleration.status.includes('failed')" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Canceled</ng-container><span *ngIf="acceleration.status === 'failed_provisional'"> ⌛</span></span>
|
<span *ngIf="acceleration.status.includes('failed') && acceleration.canceled" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Canceled</ng-container><span *ngIf="acceleration.status === 'failed_provisional'"> ⌛</span></span>
|
||||||
|
<span *ngIf="acceleration.status.includes('failed') && !acceleration.canceled" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Failed</ng-container><span *ngIf="acceleration.status === 'failed_provisional'"> ⌛</span></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="date text-right" *ngIf="!this.widget">
|
<td class="date text-right" *ngIf="!this.widget">
|
||||||
<app-time kind="since" [time]="acceleration.added" [fastRender]="true" [showTooltip]="true"></app-time>
|
<app-time kind="since" [time]="acceleration.added" [fastRender]="true" [showTooltip]="true"></app-time>
|
||||||
|
@ -478,25 +478,30 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extendSummary(summary) {
|
extendSummary(summary) {
|
||||||
let extendedSummary = summary.slice();
|
const extendedSummary = summary.slice();
|
||||||
|
|
||||||
// Add a point at today's date to make the graph end at the current time
|
// Add a point at today's date to make the graph end at the current time
|
||||||
extendedSummary.unshift({ time: Date.now() / 1000, value: 0 });
|
extendedSummary.unshift({ time: Date.now() / 1000, value: 0 });
|
||||||
extendedSummary.reverse();
|
|
||||||
|
|
||||||
let oneHour = 60 * 60;
|
let maxTime = Date.now() / 1000;
|
||||||
|
|
||||||
|
const oneHour = 60 * 60;
|
||||||
// Fill gaps longer than interval
|
// Fill gaps longer than interval
|
||||||
for (let i = 0; i < extendedSummary.length - 1; i++) {
|
for (let i = 0; i < extendedSummary.length - 1; i++) {
|
||||||
let hours = Math.floor((extendedSummary[i + 1].time - extendedSummary[i].time) / oneHour);
|
if (extendedSummary[i].time > maxTime) {
|
||||||
|
extendedSummary[i].time = maxTime - 30;
|
||||||
|
}
|
||||||
|
maxTime = extendedSummary[i].time;
|
||||||
|
const hours = Math.floor((extendedSummary[i].time - extendedSummary[i + 1].time) / oneHour);
|
||||||
if (hours > 1) {
|
if (hours > 1) {
|
||||||
for (let j = 1; j < hours; j++) {
|
for (let j = 1; j < hours; j++) {
|
||||||
let newTime = extendedSummary[i].time + oneHour * j;
|
const newTime = extendedSummary[i].time - oneHour * j;
|
||||||
extendedSummary.splice(i + j, 0, { time: newTime, value: 0 });
|
extendedSummary.splice(i + j, 0, { time: newTime, value: 0 });
|
||||||
}
|
}
|
||||||
i += hours - 1;
|
i += hours - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extendedSummary.reverse();
|
return extendedSummary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
@HostListener('document:keydown', ['$event'])
|
@HostListener('document:keydown', ['$event'])
|
||||||
handleKeyboardEvents(event: KeyboardEvent) {
|
handleKeyboardEvents(event: KeyboardEvent) {
|
||||||
if (event.target instanceof HTMLInputElement) {
|
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// prevent arrow key horizontal scrolling
|
// prevent arrow key horizontal scrolling
|
||||||
|
@ -10,6 +10,10 @@
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<app-mempool-error *ngIf="paymentErrorMessage" [error]="paymentErrorMessage"></app-mempool-error>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="paymentStatus === 2">
|
<div *ngIf="paymentStatus === 2">
|
||||||
|
|
||||||
<form [formGroup]="paymentForm">
|
<form [formGroup]="paymentForm">
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
|
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { Subscription, of, catchError } from 'rxjs';
|
||||||
import { Subscription, of, timer } from 'rxjs';
|
import { retry, tap } from 'rxjs/operators';
|
||||||
import { filter, repeat, retry, switchMap, take, tap } from 'rxjs/operators';
|
|
||||||
import { ServicesApiServices } from '@app/services/services-api.service';
|
import { ServicesApiServices } from '@app/services/services-api.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -18,30 +17,17 @@ export class BitcoinInvoiceComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Output() completed = new EventEmitter();
|
@Output() completed = new EventEmitter();
|
||||||
|
|
||||||
paymentForm: FormGroup;
|
paymentForm: FormGroup;
|
||||||
requestSubscription: Subscription | undefined;
|
|
||||||
paymentStatusSubscription: Subscription | undefined;
|
paymentStatusSubscription: Subscription | undefined;
|
||||||
paymentStatus = 1; // 1 - Waiting for invoice | 2 - Pending payment | 3 - Payment completed
|
paymentStatus = 1; // 1 - Waiting for invoice | 2 - Pending payment | 3 - Payment completed
|
||||||
paramMapSubscription: Subscription | undefined;
|
paymentErrorMessage: string = '';
|
||||||
invoiceSubscription: Subscription | undefined;
|
|
||||||
invoiceTimeout; // Wait for angular to load all the things before making a request
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private apiService: ServicesApiServices,
|
private apiService: ServicesApiServices,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer
|
||||||
private activatedRoute: ActivatedRoute
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.requestSubscription) {
|
|
||||||
this.requestSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.paramMapSubscription) {
|
|
||||||
this.paramMapSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.invoiceSubscription) {
|
|
||||||
this.invoiceSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.paymentStatusSubscription) {
|
if (this.paymentStatusSubscription) {
|
||||||
this.paymentStatusSubscription.unsubscribe();
|
this.paymentStatusSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
@ -72,15 +58,39 @@ export class BitcoinInvoiceComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.paymentStatus = 4;
|
this.paymentStatus = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.monitorPendingInvoice();
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorPendingInvoice(): void {
|
||||||
|
if (!this.invoice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.paymentStatusSubscription) {
|
||||||
|
this.paymentStatusSubscription.unsubscribe();
|
||||||
|
}
|
||||||
this.paymentStatusSubscription = this.apiService.getPaymentStatus$(this.invoice.btcpayInvoiceId).pipe(
|
this.paymentStatusSubscription = this.apiService.getPaymentStatus$(this.invoice.btcpayInvoiceId).pipe(
|
||||||
retry({ delay: () => timer(2000)}),
|
tap(result => {
|
||||||
repeat({delay: 2000}),
|
if (result.status === 204) { // Manually trigger an error in that case so we can retry
|
||||||
filter((response) => response.status !== 204 && response.status !== 404),
|
throw result;
|
||||||
take(1),
|
} else if (result.status === 200) { // Invoice settled
|
||||||
).subscribe(() => {
|
this.paymentStatus = 3;
|
||||||
this.paymentStatus = 3;
|
this.completed.emit();
|
||||||
this.completed.emit();
|
}
|
||||||
});
|
}),
|
||||||
|
catchError(err => {
|
||||||
|
if (err.status === 204 || err.status === 504) {
|
||||||
|
throw err; // Will trigger the retry
|
||||||
|
} else if (err.status === 400) {
|
||||||
|
this.paymentErrorMessage = 'Invoice has expired';
|
||||||
|
} else if (err.status === 404) {
|
||||||
|
this.paymentErrorMessage = 'Invoice is no longer valid';
|
||||||
|
}
|
||||||
|
this.paymentStatus = -1;
|
||||||
|
return of(null);
|
||||||
|
}),
|
||||||
|
retry({ delay: 1000 }),
|
||||||
|
).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
get availableMethods(): string[] {
|
get availableMethods(): string[] {
|
||||||
|
@ -172,13 +172,19 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.animationFrameRequest) {
|
if (this.animationFrameRequest) {
|
||||||
cancelAnimationFrame(this.animationFrameRequest);
|
cancelAnimationFrame(this.animationFrameRequest);
|
||||||
clearTimeout(this.animationHeartBeat);
|
|
||||||
}
|
}
|
||||||
|
clearTimeout(this.animationHeartBeat);
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||||
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||||
this.themeChangedSubscription?.unsubscribe();
|
|
||||||
}
|
}
|
||||||
|
if (this.scene) {
|
||||||
|
this.scene.destroy();
|
||||||
|
}
|
||||||
|
this.vertexArray.destroy();
|
||||||
|
this.vertexArray = null;
|
||||||
|
this.themeChangedSubscription?.unsubscribe();
|
||||||
|
this.searchSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(direction): void {
|
clear(direction): void {
|
||||||
@ -447,7 +453,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
this.applyQueuedUpdates();
|
this.applyQueuedUpdates();
|
||||||
// skip re-render if there's no change to the scene
|
// skip re-render if there's no change to the scene
|
||||||
if (this.scene && this.gl) {
|
if (this.scene && this.gl && this.vertexArray) {
|
||||||
/* SET UP SHADER UNIFORMS */
|
/* SET UP SHADER UNIFORMS */
|
||||||
// screen dimensions
|
// screen dimensions
|
||||||
this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight);
|
this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight);
|
||||||
@ -489,9 +495,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) {
|
if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) {
|
||||||
this.doRun();
|
this.doRun();
|
||||||
} else {
|
} else {
|
||||||
if (this.animationHeartBeat) {
|
clearTimeout(this.animationHeartBeat);
|
||||||
clearTimeout(this.animationHeartBeat);
|
|
||||||
}
|
|
||||||
this.animationHeartBeat = window.setTimeout(() => {
|
this.animationHeartBeat = window.setTimeout(() => {
|
||||||
this.start();
|
this.start();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -19,6 +19,7 @@ export class FastVertexArray {
|
|||||||
freeSlots: number[];
|
freeSlots: number[];
|
||||||
lastSlot: number;
|
lastSlot: number;
|
||||||
dirty = false;
|
dirty = false;
|
||||||
|
destroyed = false;
|
||||||
|
|
||||||
constructor(length, stride) {
|
constructor(length, stride) {
|
||||||
this.length = length;
|
this.length = length;
|
||||||
@ -32,6 +33,9 @@ export class FastVertexArray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insert(sprite: TxSprite): number {
|
insert(sprite: TxSprite): number {
|
||||||
|
if (this.destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.count++;
|
this.count++;
|
||||||
|
|
||||||
let position;
|
let position;
|
||||||
@ -45,11 +49,14 @@ export class FastVertexArray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.sprites[position] = sprite;
|
this.sprites[position] = sprite;
|
||||||
return position;
|
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(index: number): void {
|
remove(index: number): void {
|
||||||
|
if (this.destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.count--;
|
this.count--;
|
||||||
this.clearData(index);
|
this.clearData(index);
|
||||||
this.freeSlots.push(index);
|
this.freeSlots.push(index);
|
||||||
@ -61,20 +68,26 @@ export class FastVertexArray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setData(index: number, dataChunk: number[]): void {
|
setData(index: number, dataChunk: number[]): void {
|
||||||
|
if (this.destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.data.set(dataChunk, (index * this.stride));
|
this.data.set(dataChunk, (index * this.stride));
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearData(index: number): void {
|
private clearData(index: number): void {
|
||||||
this.data.fill(0, (index * this.stride), ((index + 1) * this.stride));
|
this.data.fill(0, (index * this.stride), ((index + 1) * this.stride));
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getData(index: number): Float32Array {
|
getData(index: number): Float32Array {
|
||||||
|
if (this.destroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return this.data.subarray(index, this.stride);
|
return this.data.subarray(index, this.stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
expand(): void {
|
private expand(): void {
|
||||||
this.length *= 2;
|
this.length *= 2;
|
||||||
const newData = new Float32Array(this.length * this.stride);
|
const newData = new Float32Array(this.length * this.stride);
|
||||||
newData.set(this.data);
|
newData.set(this.data);
|
||||||
@ -82,7 +95,7 @@ export class FastVertexArray {
|
|||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
compact(): void {
|
private compact(): void {
|
||||||
// New array length is the smallest power of 2 larger than the sprite count (but no smaller than 512)
|
// New array length is the smallest power of 2 larger than the sprite count (but no smaller than 512)
|
||||||
const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count))));
|
const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count))));
|
||||||
if (newLength !== this.length) {
|
if (newLength !== this.length) {
|
||||||
@ -110,4 +123,13 @@ export class FastVertexArray {
|
|||||||
getVertexData(): Float32Array {
|
getVertexData(): Float32Array {
|
||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
this.data = null;
|
||||||
|
this.sprites = null;
|
||||||
|
this.freeSlots = null;
|
||||||
|
this.lastSlot = 0;
|
||||||
|
this.dirty = false;
|
||||||
|
this.destroyed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ export class BlockViewComponent implements OnInit, OnDestroy {
|
|||||||
this.isLoadingBlock = false;
|
this.isLoadingBlock = false;
|
||||||
this.isLoadingOverview = true;
|
this.isLoadingOverview = true;
|
||||||
}),
|
}),
|
||||||
shareReplay(1)
|
shareReplay({ bufferSize: 1, refCount: true })
|
||||||
);
|
);
|
||||||
|
|
||||||
this.overviewSubscription = block$.pipe(
|
this.overviewSubscription = block$.pipe(
|
||||||
@ -176,5 +176,8 @@ export class BlockViewComponent implements OnInit, OnDestroy {
|
|||||||
if (this.queryParamsSubscription) {
|
if (this.queryParamsSubscription) {
|
||||||
this.queryParamsSubscription.unsubscribe();
|
this.queryParamsSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
if (this.blockGraph) {
|
||||||
|
this.blockGraph.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
|
|||||||
this.openGraphService.waitOver('block-data-' + this.rawId);
|
this.openGraphService.waitOver('block-data-' + this.rawId);
|
||||||
}),
|
}),
|
||||||
throttleTime(50, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(50, asyncScheduler, { leading: true, trailing: true }),
|
||||||
shareReplay(1)
|
shareReplay({ bufferSize: 1, refCount: true })
|
||||||
);
|
);
|
||||||
|
|
||||||
this.overviewSubscription = block$.pipe(
|
this.overviewSubscription = block$.pipe(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Params, Router } from '@angular/router';
|
||||||
import { ElectrsApiService } from '@app/services/electrs-api.service';
|
import { ElectrsApiService } from '@app/services/electrs-api.service';
|
||||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators';
|
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter, take } from 'rxjs/operators';
|
||||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
||||||
import { StateService } from '@app/services/state.service';
|
import { StateService } from '@app/services/state.service';
|
||||||
import { SeoService } from '@app/services/seo.service';
|
import { SeoService } from '@app/services/seo.service';
|
||||||
@ -68,6 +68,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||||
numUnexpected: number = 0;
|
numUnexpected: number = 0;
|
||||||
mode: 'projected' | 'actual' = 'projected';
|
mode: 'projected' | 'actual' = 'projected';
|
||||||
|
currentQueryParams: Params;
|
||||||
|
|
||||||
overviewSubscription: Subscription;
|
overviewSubscription: Subscription;
|
||||||
accelerationsSubscription: Subscription;
|
accelerationsSubscription: Subscription;
|
||||||
@ -80,8 +81,8 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
timeLtr: boolean;
|
timeLtr: boolean;
|
||||||
childChangeSubscription: Subscription;
|
childChangeSubscription: Subscription;
|
||||||
auditPrefSubscription: Subscription;
|
auditPrefSubscription: Subscription;
|
||||||
|
isAuditEnabledSubscription: Subscription;
|
||||||
oobSubscription: Subscription;
|
oobSubscription: Subscription;
|
||||||
|
|
||||||
priceSubscription: Subscription;
|
priceSubscription: Subscription;
|
||||||
blockConversion: Price;
|
blockConversion: Price;
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.setAuditAvailable(this.auditSupported);
|
this.setAuditAvailable(this.auditSupported);
|
||||||
|
|
||||||
if (this.auditSupported) {
|
if (this.auditSupported) {
|
||||||
this.isAuditEnabledFromParam().subscribe(auditParam => {
|
this.isAuditEnabledSubscription = this.isAuditEnabledFromParam().subscribe(auditParam => {
|
||||||
if (this.auditParamEnabled) {
|
if (this.auditParamEnabled) {
|
||||||
this.auditModeEnabled = auditParam;
|
this.auditModeEnabled = auditParam;
|
||||||
} else {
|
} else {
|
||||||
@ -281,7 +282,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
throttleTime(300, asyncScheduler, { leading: true, trailing: true }),
|
throttleTime(300, asyncScheduler, { leading: true, trailing: true }),
|
||||||
shareReplay(1)
|
shareReplay({ bufferSize: 1, refCount: true })
|
||||||
);
|
);
|
||||||
|
|
||||||
this.overviewSubscription = this.block$.pipe(
|
this.overviewSubscription = this.block$.pipe(
|
||||||
@ -363,6 +364,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe((network) => this.network = network);
|
.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||||
|
this.currentQueryParams = params;
|
||||||
if (params.showDetails === 'true') {
|
if (params.showDetails === 'true') {
|
||||||
this.showDetails = true;
|
this.showDetails = true;
|
||||||
} else {
|
} else {
|
||||||
@ -414,6 +416,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.stateService.markBlock$.next({});
|
this.stateService.markBlock$.next({});
|
||||||
this.overviewSubscription?.unsubscribe();
|
this.overviewSubscription?.unsubscribe();
|
||||||
|
this.accelerationsSubscription?.unsubscribe();
|
||||||
this.keyNavigationSubscription?.unsubscribe();
|
this.keyNavigationSubscription?.unsubscribe();
|
||||||
this.blocksSubscription?.unsubscribe();
|
this.blocksSubscription?.unsubscribe();
|
||||||
this.cacheBlocksSubscription?.unsubscribe();
|
this.cacheBlocksSubscription?.unsubscribe();
|
||||||
@ -421,8 +424,16 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.queryParamsSubscription?.unsubscribe();
|
this.queryParamsSubscription?.unsubscribe();
|
||||||
this.timeLtrSubscription?.unsubscribe();
|
this.timeLtrSubscription?.unsubscribe();
|
||||||
this.childChangeSubscription?.unsubscribe();
|
this.childChangeSubscription?.unsubscribe();
|
||||||
this.priceSubscription?.unsubscribe();
|
this.auditPrefSubscription?.unsubscribe();
|
||||||
|
this.isAuditEnabledSubscription?.unsubscribe();
|
||||||
this.oobSubscription?.unsubscribe();
|
this.oobSubscription?.unsubscribe();
|
||||||
|
this.priceSubscription?.unsubscribe();
|
||||||
|
this.blockGraphProjected.forEach(graph => {
|
||||||
|
graph.destroy();
|
||||||
|
});
|
||||||
|
this.blockGraphActual.forEach(graph => {
|
||||||
|
graph.destroy();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Refactor this.fees/this.reward for liquid because it is not
|
// TODO - Refactor this.fees/this.reward for liquid because it is not
|
||||||
@ -733,19 +744,18 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
toggleAuditMode(): void {
|
toggleAuditMode(): void {
|
||||||
this.stateService.hideAudit.next(this.auditModeEnabled);
|
this.stateService.hideAudit.next(this.auditModeEnabled);
|
||||||
|
|
||||||
this.route.queryParams.subscribe(params => {
|
const queryParams = { ...this.currentQueryParams };
|
||||||
const queryParams = { ...params };
|
delete queryParams['audit'];
|
||||||
delete queryParams['audit'];
|
|
||||||
|
|
||||||
let newUrl = this.router.url.split('?')[0];
|
let newUrl = this.router.url.split('?')[0];
|
||||||
const queryString = new URLSearchParams(queryParams).toString();
|
const queryString = new URLSearchParams(queryParams).toString();
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
newUrl += '?' + queryString;
|
newUrl += '?' + queryString;
|
||||||
}
|
}
|
||||||
|
this.location.replaceState(newUrl);
|
||||||
this.location.replaceState(newUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// avoid duplicate subscriptions
|
||||||
|
this.auditPrefSubscription?.unsubscribe();
|
||||||
this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => {
|
this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => {
|
||||||
this.auditModeEnabled = !hide;
|
this.auditModeEnabled = !hide;
|
||||||
this.showAudit = this.auditAvailable && this.auditModeEnabled;
|
this.showAudit = this.auditAvailable && this.auditModeEnabled;
|
||||||
@ -762,7 +772,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
return this.route.queryParams.pipe(
|
return this.route.queryParams.pipe(
|
||||||
map(params => {
|
map(params => {
|
||||||
this.auditParamEnabled = 'audit' in params;
|
this.auditParamEnabled = 'audit' in params;
|
||||||
|
|
||||||
return this.auditParamEnabled ? !(params['audit'] === 'false') : true;
|
return this.auditParamEnabled ? !(params['audit'] === 'false') : true;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -281,9 +281,11 @@
|
|||||||
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<span class="title-link">
|
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/wallet/' + widget.props.wallet | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="dashboard.treasury-transactions">Treasury Transactions</h5>
|
<h5 class="card-title d-inline" i18n="dashboard.treasury-transactions">Treasury Transactions</h5>
|
||||||
</span>
|
<span> </span>
|
||||||
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
|
</a>
|
||||||
<app-address-transactions-widget [addressSummary$]="walletSummary$"></app-address-transactions-widget>
|
<app-address-transactions-widget [addressSummary$]="walletSummary$"></app-address-transactions-widget>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -162,6 +162,9 @@ export class EightBlocksComponent implements OnInit, OnDestroy {
|
|||||||
this.cacheBlocksSubscription?.unsubscribe();
|
this.cacheBlocksSubscription?.unsubscribe();
|
||||||
this.networkChangedSubscription?.unsubscribe();
|
this.networkChangedSubscription?.unsubscribe();
|
||||||
this.queryParamsSubscription?.unsubscribe();
|
this.queryParamsSubscription?.unsubscribe();
|
||||||
|
this.blockGraphs.forEach(graph => {
|
||||||
|
graph.destroy();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shiftTestBlocks(): void {
|
shiftTestBlocks(): void {
|
||||||
|
@ -19,12 +19,10 @@
|
|||||||
} @else if (!user) {
|
} @else if (!user) {
|
||||||
<!-- User not logged in -->
|
<!-- User not logged in -->
|
||||||
<div class="alert alert-mempool d-block text-center w-100">
|
<div class="alert alert-mempool d-block text-center w-100">
|
||||||
<div class="d-inline align-middle">
|
<div class="d-inline align-middle pr-2">
|
||||||
<span>To use the faucet, please </span>
|
<span>To use the faucet, please</span>
|
||||||
<a routerLink="/login" [queryParams]="{'redirectTo': '/testnet4/faucet'}">login</a>
|
|
||||||
<span class="mr-2"> or</span>
|
|
||||||
</div>
|
</div>
|
||||||
<app-twitter-login customClass="btn btn-sm" width="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Twitter"></app-twitter-login>
|
<app-github-login customClass="btn btn-sm" width="150px" redirectTo="/testnet4/faucet" buttonString="Sign up with"></app-github-login>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@else if (user && user.status === 'pending' && !user.email && user.snsId) {
|
@else if (user && user.status === 'pending' && !user.email && user.snsId) {
|
||||||
@ -36,18 +34,18 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@else if (error === 'not_available') {
|
@else if (error === 'not_available') {
|
||||||
<!-- User logged in but not a paid user or did not link its Twitter account -->
|
<!-- User logged in but not a paid user or did not link its Github account -->
|
||||||
<div class="alert alert-mempool d-block text-center w-100">
|
<div class="alert alert-mempool d-block text-center w-100">
|
||||||
<div class="d-inline align-middle">
|
<div class="d-inline align-middle">
|
||||||
<span class="mb-2 mr-2">To use the faucet, please</span>
|
<span class="mb-2 mr-2">To use the faucet, please</span>
|
||||||
</div>
|
</div>
|
||||||
<app-twitter-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your Twitter"></app-twitter-login>
|
<app-github-login customClass="btn btn-sm" width="180px" redirectTo="/testnet4/faucet" buttonString="Link your"></app-github-login>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@else if (error === 'account_limited') {
|
@else if (error === 'account_limited') {
|
||||||
<div class="alert alert-mempool d-block text-center w-100">
|
<div class="alert alert-mempool d-block text-center w-100">
|
||||||
<div class="d-inline align-middle">
|
<div class="d-inline align-middle">
|
||||||
<span class="mb-2 mr-2">Your Twitter account does not allow you to access the faucet</span>
|
<span class="mb-2 mr-2">Your account does not allow you to access the faucet</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ export class FaucetComponent implements OnInit, OnDestroy {
|
|||||||
min: number; // minimum amount to request at once (in sats)
|
min: number; // minimum amount to request at once (in sats)
|
||||||
max: number; // maximum amount to request at once
|
max: number; // maximum amount to request at once
|
||||||
address?: string; // faucet address
|
address?: string; // faucet address
|
||||||
code: 'ok' | 'faucet_not_available' | 'faucet_maximum_reached' | 'faucet_too_soon';
|
code: 'ok' | 'faucet_not_available' | 'faucet_maximum_reached' | 'faucet_too_soon' | 'faucet_not_available_no_utxo';
|
||||||
} | null = null;
|
} | null = null;
|
||||||
faucetForm: FormGroup;
|
faucetForm: FormGroup;
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
<a href="#" (click)="githubLogin()" [class]="(disabled ? 'disabled': '') + (customClass ? customClass : 'w-100 btn mt-1 d-flex justify-content-center align-items-center')" style="background-color: rgb(31, 35, 40)" [style]="width ? 'width: ' + width : ''">
|
||||||
|
<span class="ml-2 text-light align-middle">{{ buttonString }}</span>
|
||||||
|
<svg height="32" viewBox="0 0 18 16" width="32" style="fill: white; padding-left: 5px">
|
||||||
|
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'app-github-login',
|
||||||
|
templateUrl: './github-login.component.html',
|
||||||
|
})
|
||||||
|
export class GithubLogin {
|
||||||
|
@Input() width: string | null = null;
|
||||||
|
@Input() customClass: string | null = null;
|
||||||
|
@Input() buttonString: string= 'unset';
|
||||||
|
@Input() redirectTo: string | null = null;
|
||||||
|
@Output() clicked = new EventEmitter<boolean>();
|
||||||
|
@Input() disabled: boolean = false;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
githubLogin() {
|
||||||
|
this.clicked.emit(true);
|
||||||
|
if (this.redirectTo) {
|
||||||
|
location.replace(`/api/v1/services/auth/login/github?redirectTo=${encodeURIComponent(this.redirectTo)}`);
|
||||||
|
} else {
|
||||||
|
location.replace(`/api/v1/services/auth/login/github?redirectTo=${location.href}`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,8 @@
|
|||||||
<nav class="navbar navbar-expand-md navbar-dark">
|
<nav class="navbar navbar-expand-md navbar-dark">
|
||||||
<!-- Hamburger -->
|
<!-- Hamburger -->
|
||||||
<ng-container *ngIf="servicesEnabled">
|
<ng-container *ngIf="servicesEnabled">
|
||||||
<div *ngIf="user" class="profile_image_container" [class]="{'anon': !user.imageMd5}" (click)="hamburgerClick($event)">
|
<div *ngIf="user" class="profile_image_container" (click)="hamburgerClick($event)">
|
||||||
<img *ngIf="user.imageMd5" [src]="'/api/v1/services/account/images/' + user.username + '/md5=' + user.imageMd5" class="profile_image">
|
<img [src]="'/api/v1/services/account/images/' + user.username" class="profile_image" onError="this.src = '/resources/anon.svg'; this.className = 'anon'" />
|
||||||
<app-svg-images style="color: lightgrey; fill: lightgray" *ngIf="!user.imageMd5" name="anon"></app-svg-images>
|
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="false && user === null" class="profile_image_container" (click)="hamburgerClick($event)">
|
<div *ngIf="false && user === null" class="profile_image_container" (click)="hamburgerClick($event)">
|
||||||
<app-svg-images name="hamburger" height="40"></app-svg-images>
|
<app-svg-images name="hamburger" height="40"></app-svg-images>
|
||||||
@ -23,7 +22,7 @@
|
|||||||
} @else {
|
} @else {
|
||||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||||
<div class="subdomain_container">
|
<div class="subdomain_container">
|
||||||
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo'" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
</div>
|
</div>
|
||||||
<div class="vertical-line"></div>
|
<div class="vertical-line"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -43,7 +42,7 @@
|
|||||||
} @else {
|
} @else {
|
||||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||||
<div class="subdomain_container">
|
<div class="subdomain_container">
|
||||||
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo'" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
</div>
|
</div>
|
||||||
<div class="vertical-line"></div>
|
<div class="vertical-line"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -269,7 +269,7 @@ nav {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&.anon {
|
.anon {
|
||||||
border: 1.5px solid lightgrey;
|
border: 1.5px solid lightgrey;
|
||||||
color: lightgrey;
|
color: lightgrey;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@ -120,6 +120,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
this.blockGraph?.destroy();
|
||||||
this.blockSub.unsubscribe();
|
this.blockSub.unsubscribe();
|
||||||
this.timeLtrSubscription.unsubscribe();
|
this.timeLtrSubscription.unsubscribe();
|
||||||
this.websocketService.stopTrackMempoolBlock();
|
this.websocketService.stopTrackMempoolBlock();
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1>
|
<h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box pool-details">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
@ -173,7 +173,125 @@
|
|||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Stratum Job -->
|
||||||
|
<ng-container *ngIf="(job$ | async) as job;">
|
||||||
|
<h2 i18n="pool.next_block">Next block</h2>
|
||||||
|
<div class="box mb-3">
|
||||||
|
<div class="row" >
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-borderless table-striped">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="job-table table table-xs table-borderless table-fixed table-data">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="data-title clip text-center height" i18n="latest-blocks.height">Height</th>
|
||||||
|
<th class="data-title clip text-center expected" i18n="next-block.expected-time">Expected</th>
|
||||||
|
<th class="data-title clip text-center reward" i18n="latest-blocks.reward">Reward</th>
|
||||||
|
<th class="data-title clip text-center timestamp" i18n="next-block.timestamp">Timestamp</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center height">
|
||||||
|
{{ job.height }}
|
||||||
|
</td>
|
||||||
|
<td class="text-center expected">
|
||||||
|
<ng-container *ngIf="(expectedBlockTime$ | async) as expectedBlockTime; else expectedPlaceholder">
|
||||||
|
<app-time kind="until" [time]="expectedBlockTime" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #expectedPlaceholder>~</ng-template>
|
||||||
|
</td>
|
||||||
|
<td class="text-center reward">
|
||||||
|
<app-amount [satoshis]="job.reward"></app-amount>
|
||||||
|
</td>
|
||||||
|
<td class="text-center timestamp">
|
||||||
|
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="job.timestamp" [precision]="1" minUnit="minute" [hideTimeSince]="true"></app-timestamp>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="job-table table table-xs table-borderless table-fixed table-data">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="data-title clip text-center coinbase" i18n="latest-blocks.coinbasetag">Coinbase tag</th>
|
||||||
|
<th class="data-title clip text-center clean" i18n="next-block.clean">Clean</th>
|
||||||
|
<th class="data-title clip text-center prevhash" i18n="next-block.prevhash">Prevhash</th>
|
||||||
|
<th class="data-title clip text-center job-received" i18n="next-block.job-received">Job Received</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center coinbase">
|
||||||
|
{{ job.scriptsig | hex2ascii }}
|
||||||
|
</td>
|
||||||
|
<td class="text-center clean">
|
||||||
|
@if (job.cleanJobs) {
|
||||||
|
<fa-icon [icon]="['fas', 'check-circle']" [fixedWidth]="true"></fa-icon>
|
||||||
|
} @else {
|
||||||
|
<fa-icon [icon]="['fas', 'times-circle']" [fixedWidth]="true"></fa-icon>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="text-center prevhash">
|
||||||
|
<a [routerLink]="['/block' | relativeUrl, job.prevHash]">
|
||||||
|
<app-truncate [text]="job.prevHash" [lastChars]="8"></app-truncate>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="text-center job-received">
|
||||||
|
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="job.received / 1000" [precision]="1" minUnit="minute" [hideTimeSince]="true"></app-timestamp>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="stratum-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="data-title clip text-center" [attr.colspan]="Math.max(job.merkleBranches.length, 12)">
|
||||||
|
<a class="title-link" href="" [routerLink]="['/stratum' | relativeUrl]">
|
||||||
|
Merkle Branches
|
||||||
|
<span> </span>
|
||||||
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
@for (branch of job.merkleBranches; track $index) {
|
||||||
|
<td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''">
|
||||||
|
@if ($index === 0 && branch) {
|
||||||
|
<a [routerLink]="['/tx' | relativeUrl, reverseHash(branch)]" class="cell-link">
|
||||||
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 14px; color: white"></fa-icon>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
@for (_ of [].constructor(Math.max(0, 12 - job.merkleBranches.length)); track $index) {
|
||||||
|
<td class="merkle empty-branch"></td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Blocks list -->
|
<!-- Blocks list -->
|
||||||
|
<h2 i18n="master-page.blocks">Blocks</h2>
|
||||||
<table class="table table-borderless" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5"
|
<table class="table table-borderless" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5"
|
||||||
[infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="loadMore()">
|
[infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="loadMore()">
|
||||||
<ng-container *ngIf="blocks$ | async as blocks; else skeleton">
|
<ng-container *ngIf="blocks$ | async as blocks; else skeleton">
|
||||||
|
@ -49,111 +49,110 @@ div.scrollable {
|
|||||||
max-height: 75px;
|
max-height: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box {
|
.pool-details {
|
||||||
padding-bottom: 5px;
|
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 767.98px) {
|
||||||
min-height: 187px;
|
min-height: 187px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 767.98px) {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 767.98px) {
|
.label.addresses {
|
||||||
font-weight: bold;
|
vertical-align: top;
|
||||||
|
padding-top: 25px;
|
||||||
|
}
|
||||||
|
.addresses-data {
|
||||||
|
vertical-align: top;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.label.addresses {
|
|
||||||
vertical-align: top;
|
|
||||||
padding-top: 25px;
|
|
||||||
}
|
|
||||||
.addresses-data {
|
|
||||||
vertical-align: top;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data {
|
.data {
|
||||||
text-align: right;
|
|
||||||
padding-left: 5%;
|
|
||||||
@media (max-width: 992px) {
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
@media (max-width: 450px) {
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
padding-left: 5%;
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.coinbase {
|
.coinbase {
|
||||||
width: 20%;
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.height {
|
|
||||||
width: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
padding-left: 50px;
|
|
||||||
}
|
|
||||||
@media (max-width: 685px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mined {
|
|
||||||
width: 13%;
|
|
||||||
@media (max-width: 1100px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.txs {
|
|
||||||
padding-right: 40px;
|
|
||||||
@media (max-width: 1100px) {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
@media (max-width: 567px) {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.size {
|
|
||||||
width: 12%;
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
width: 15%;
|
|
||||||
}
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
@media (max-width: 875px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 650px) {
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
@media (max-width: 450px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scriptmessage {
|
.height {
|
||||||
overflow: hidden;
|
width: 10%;
|
||||||
display: inline-block;
|
}
|
||||||
text-overflow: ellipsis;
|
|
||||||
vertical-align: middle;
|
.timestamp {
|
||||||
width: auto;
|
@media (max-width: 875px) {
|
||||||
text-align: left;
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
@media (max-width: 685px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mined {
|
||||||
|
width: 13%;
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.txs {
|
||||||
|
padding-right: 40px;
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
@media (max-width: 875px) {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
@media (max-width: 567px) {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.size {
|
||||||
|
width: 12%;
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
@media (max-width: 875px) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scriptmessage {
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: auto;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-loader {
|
.skeleton-loader {
|
||||||
@ -214,4 +213,69 @@ div.scrollable {
|
|||||||
|
|
||||||
.taller-row {
|
.taller-row {
|
||||||
height: 75px;
|
height: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stratum-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.merkle {
|
||||||
|
width: 100px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-branch {
|
||||||
|
outline: solid 1px white;
|
||||||
|
outline-offset: -1px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(to top left, transparent, transparent 48%, white 49%, white 51%, transparent 52%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
position: relative;
|
||||||
|
height: 2em;
|
||||||
|
|
||||||
|
.cell-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-table {
|
||||||
|
td, th {
|
||||||
|
width: 25%;
|
||||||
|
max-width: 25%;
|
||||||
|
min-width: 25%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0.1rem 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.expected, .timestamp, .clean, .job-received {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-link, .title-link:hover, .title-link:focus, .title-link:active {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
@ -10,6 +10,9 @@ import { selectPowerOfTen } from '@app/bitcoin.utils';
|
|||||||
import { formatNumber } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
import { SeoService } from '@app/services/seo.service';
|
import { SeoService } from '@app/services/seo.service';
|
||||||
import { HttpErrorResponse } from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { StratumJob } from '../../interfaces/websocket.interface';
|
||||||
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
|
import { MiningService } from '../../services/mining.service';
|
||||||
|
|
||||||
interface AccelerationTotal {
|
interface AccelerationTotal {
|
||||||
cost: number,
|
cost: number,
|
||||||
@ -27,12 +30,16 @@ export class PoolComponent implements OnInit {
|
|||||||
@Input() left: number | string = 75;
|
@Input() left: number | string = 75;
|
||||||
|
|
||||||
gfg = true;
|
gfg = true;
|
||||||
|
stratumEnabled = this.stateService.env.STRATUM_ENABLED;
|
||||||
|
|
||||||
formatNumber = formatNumber;
|
formatNumber = formatNumber;
|
||||||
|
Math = Math;
|
||||||
slugSubscription: Subscription;
|
slugSubscription: Subscription;
|
||||||
poolStats$: Observable<PoolStat>;
|
poolStats$: Observable<PoolStat>;
|
||||||
blocks$: Observable<BlockExtended[]>;
|
blocks$: Observable<BlockExtended[]>;
|
||||||
oobFees$: Observable<AccelerationTotal[]>;
|
oobFees$: Observable<AccelerationTotal[]>;
|
||||||
|
job$: Observable<StratumJob | null>;
|
||||||
|
expectedBlockTime$: Observable<number>;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
error: HttpErrorResponse | null = null;
|
error: HttpErrorResponse | null = null;
|
||||||
|
|
||||||
@ -53,6 +60,8 @@ export class PoolComponent implements OnInit {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private miningService: MiningService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
) {
|
) {
|
||||||
this.auditAvailable = this.stateService.env.AUDIT;
|
this.auditAvailable = this.stateService.env.AUDIT;
|
||||||
@ -62,7 +71,7 @@ export class PoolComponent implements OnInit {
|
|||||||
this.slugSubscription = this.route.params.pipe(map((params) => params.slug)).subscribe((slug) => {
|
this.slugSubscription = this.route.params.pipe(map((params) => params.slug)).subscribe((slug) => {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.blocks = [];
|
this.blocks = [];
|
||||||
this.chartOptions = {};
|
this.chartOptions = {};
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
this.initializeObservables();
|
this.initializeObservables();
|
||||||
});
|
});
|
||||||
@ -129,6 +138,31 @@ export class PoolComponent implements OnInit {
|
|||||||
}),
|
}),
|
||||||
filter(oob => oob.length === 3 && oob[2].count > 0)
|
filter(oob => oob.length === 3 && oob[2].count > 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.stratumEnabled) {
|
||||||
|
this.job$ = combineLatest([
|
||||||
|
this.poolStats$.pipe(
|
||||||
|
tap((poolStats) => {
|
||||||
|
this.websocketService.startTrackStratum(poolStats.pool.unique_id);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
this.stateService.stratumJobs$
|
||||||
|
]).pipe(
|
||||||
|
map(([poolStats, jobs]) => {
|
||||||
|
return jobs[poolStats.pool.unique_id];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.expectedBlockTime$ = combineLatest([
|
||||||
|
this.miningService.getMiningStats('1w'),
|
||||||
|
this.poolStats$,
|
||||||
|
this.stateService.difficultyAdjustment$
|
||||||
|
]).pipe(
|
||||||
|
map(([miningStats, poolStat, da]) => {
|
||||||
|
return (da.timeAvg / ((poolStat.estimatedHashrate || 0) / (miningStats.lastEstimatedHashrate * 1_000_000_000_000_000_000))) + Date.now() + da.timeOffset;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(hashrate, share) {
|
prepareChartOptions(hashrate, share) {
|
||||||
@ -327,6 +361,10 @@ export class PoolComponent implements OnInit {
|
|||||||
return block.height;
|
return block.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reverseHash(hash: string) {
|
||||||
|
return hash.match(/../g).reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.slugSubscription.unsubscribe();
|
this.slugSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,10 @@ export class ServerHealthComponent implements OnInit {
|
|||||||
return '🇺🇸';
|
return '🇺🇸';
|
||||||
} else if (host.includes('.va1.')) {
|
} else if (host.includes('.va1.')) {
|
||||||
return '🇺🇸';
|
return '🇺🇸';
|
||||||
|
} else if (host.includes('.sg1.')) {
|
||||||
|
return '🇸🇬';
|
||||||
|
} else if (host.includes('.hnl.')) {
|
||||||
|
return '🤙';
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
<div class="container-xl" style="min-height: 335px">
|
||||||
|
<h1 class="float-left" i18n="master-page.blocks">Stratum Jobs</h1>
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<div style="min-height: 295px">
|
||||||
|
<table *ngIf="poolsReady && (rows$ | async) as rows;" class="stratum-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td class="merkle" [attr.colspan]="rows[0]?.merkleCells?.length || 4">
|
||||||
|
Merkle Branches
|
||||||
|
</td>
|
||||||
|
<td class="pool">Pool</td>
|
||||||
|
<td class="tag">Coinbase Tag</td>
|
||||||
|
<td class="reward">Reward</td>
|
||||||
|
<td class="height">Height</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (row of rows; track row.job.pool) {
|
||||||
|
<tr>
|
||||||
|
@for (cell of row.merkleCells; track $index) {
|
||||||
|
<td class="merkle" [style.background-color]="cell.hash ? '#' + cell.hash.slice(0, 6) : ''">
|
||||||
|
@if ($index === 0 && cell.hash) {
|
||||||
|
<a [routerLink]="['/tx' | relativeUrl, reverseHash(cell.hash)]" class="cell-link">
|
||||||
|
<div class="pipe-segment" [class]="pipeToClass(cell.type)"></div>
|
||||||
|
</a>
|
||||||
|
} @else {
|
||||||
|
<div class="pipe-segment" [class]="pipeToClass(cell.type)"></div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
<td class="pool">
|
||||||
|
@if (pools[row.job.pool]) {
|
||||||
|
<a class="badge" [routerLink]="[('/mining/pool/' + pools[row.job.pool].slug) | relativeUrl]">
|
||||||
|
<img class="pool-logo" [src]="'/resources/mining-pools/' + pools[row.job.pool].slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pools[row.job.pool].name + ' mining pool'">
|
||||||
|
{{ pools[row.job.pool].name}}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="tag">
|
||||||
|
{{ row.job.tag }}
|
||||||
|
</td>
|
||||||
|
<td class="reward">
|
||||||
|
<app-amount [satoshis]="row.job.reward"></app-amount>
|
||||||
|
</td>
|
||||||
|
<td class="height">
|
||||||
|
{{ row.job.height }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,138 @@
|
|||||||
|
.stratum-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
position: relative;
|
||||||
|
height: 2em;
|
||||||
|
|
||||||
|
&.height, &.reward, &.tag {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tag {
|
||||||
|
max-width: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pool {
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.merkle {
|
||||||
|
width: 100px;
|
||||||
|
.pipe-segment {
|
||||||
|
position: absolute;
|
||||||
|
border-color: white;
|
||||||
|
box-sizing: content-box;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
border-left: solid 4px;
|
||||||
|
}
|
||||||
|
&.horizontal {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
border-top: solid 4px;
|
||||||
|
}
|
||||||
|
&.branch-top {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
border-top: solid 4px;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: content-box;
|
||||||
|
top: -4px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 50%;
|
||||||
|
border-top: solid 4px;
|
||||||
|
border-left: solid 4px;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.branch-mid {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0px;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
border-left: solid 4px;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: content-box;
|
||||||
|
top: -4px;
|
||||||
|
left: -4px;
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
border-bottom: solid 4px;
|
||||||
|
border-left: solid 4px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.branch-end {
|
||||||
|
top: -4px;
|
||||||
|
right: 0;
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
border-bottom: solid 4px;
|
||||||
|
border-left: solid 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.stratum-table {
|
||||||
|
td {
|
||||||
|
&.tag {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
.stratum-table {
|
||||||
|
td {
|
||||||
|
&.reward {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
position: relative;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pool-logo {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
@ -0,0 +1,230 @@
|
|||||||
|
import { Component, OnInit, ChangeDetectionStrategy, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { StateService } from '../../../services/state.service';
|
||||||
|
import { WebsocketService } from '../../../services/websocket.service';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { StratumJob } from '../../../interfaces/websocket.interface';
|
||||||
|
import { MiningService } from '../../../services/mining.service';
|
||||||
|
import { SinglePoolStats } from '../../../interfaces/node-api.interface';
|
||||||
|
|
||||||
|
type MerkleCellType = ' ' | '┬' | '├' | '└' | '│' | '─' | 'leaf';
|
||||||
|
|
||||||
|
|
||||||
|
interface TaggedStratumJob extends StratumJob {
|
||||||
|
tag: string;
|
||||||
|
merkleBranchIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MerkleCell {
|
||||||
|
hash: string;
|
||||||
|
type: MerkleCellType;
|
||||||
|
job?: TaggedStratumJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MerkleTree {
|
||||||
|
hash?: string;
|
||||||
|
job: string;
|
||||||
|
size: number;
|
||||||
|
children?: MerkleTree[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PoolRow {
|
||||||
|
job: TaggedStratumJob;
|
||||||
|
merkleCells: MerkleCell[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTag(scriptSig: string): string {
|
||||||
|
const hex = scriptSig.slice(8).replace(/6d6d.{64}/, '');
|
||||||
|
const bytes: number[] = [];
|
||||||
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
|
bytes.push(parseInt(hex.substr(i, 2), 16));
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const ascii = new TextDecoder('utf8').decode(Uint8Array.from(bytes)).replace(/\uFFFD/g, '').replace(/\\0/g, '').replace(/[\x00-\x1F\x7F-\x9F]/g, '');
|
||||||
|
if (ascii.includes('/ViaBTC/')) {
|
||||||
|
return '/ViaBTC/';
|
||||||
|
} else if (ascii.includes('SpiderPool/')) {
|
||||||
|
return 'SpiderPool/';
|
||||||
|
}
|
||||||
|
return (ascii.match(/\/.*\//)?.[0] || ascii).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMerkleBranchIds(merkleBranches: string[], numBranches: number, poolId: number): string[] {
|
||||||
|
let lastHash = '';
|
||||||
|
const ids: string[] = [];
|
||||||
|
for (let i = 0; i < numBranches; i++) {
|
||||||
|
if (merkleBranches[i]) {
|
||||||
|
lastHash = merkleBranches[i];
|
||||||
|
ids.push(`${i}-${lastHash}`);
|
||||||
|
} else {
|
||||||
|
ids.push(`${i}-${lastHash}-${poolId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-stratum-list',
|
||||||
|
templateUrl: './stratum-list.component.html',
|
||||||
|
styleUrls: ['./stratum-list.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class StratumList implements OnInit, OnDestroy {
|
||||||
|
rows$: Observable<PoolRow[]>;
|
||||||
|
pools: { [id: number]: SinglePoolStats } = {};
|
||||||
|
poolsReady: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private stateService: StateService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private miningService: MiningService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['stats', 'blocks', 'mempool-blocks']);
|
||||||
|
this.miningService.getPools().subscribe(pools => {
|
||||||
|
this.pools = {};
|
||||||
|
for (const pool of pools) {
|
||||||
|
this.pools[pool.unique_id] = pool;
|
||||||
|
}
|
||||||
|
this.poolsReady = true;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
this.rows$ = this.stateService.stratumJobs$.pipe(
|
||||||
|
map((jobs) => this.processJobs(jobs)),
|
||||||
|
);
|
||||||
|
this.websocketService.startTrackStratum('all');
|
||||||
|
}
|
||||||
|
|
||||||
|
processJobs(rawJobs: Record<string, StratumJob>): PoolRow[] {
|
||||||
|
const numBranches = Math.max(...Object.values(rawJobs).map(job => job.merkleBranches.length));
|
||||||
|
const jobs: Record<string, TaggedStratumJob> = {};
|
||||||
|
for (const [id, job] of Object.entries(rawJobs)) {
|
||||||
|
jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches, job.pool) };
|
||||||
|
}
|
||||||
|
if (Object.keys(jobs).length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let trees: MerkleTree[] = Object.keys(jobs).map(job => ({
|
||||||
|
job,
|
||||||
|
size: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// build tree from bottom up
|
||||||
|
for (let col = numBranches - 1; col >= 0; col--) {
|
||||||
|
const groups: Record<string, MerkleTree[]> = {};
|
||||||
|
for (const tree of trees) {
|
||||||
|
const branchId = jobs[tree.job].merkleBranchIds[col];
|
||||||
|
if (!groups[branchId]) {
|
||||||
|
groups[branchId] = [];
|
||||||
|
}
|
||||||
|
groups[branchId].push(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
trees = Object.values(groups).map(group => ({
|
||||||
|
hash: jobs[group[0].job].merkleBranches[col],
|
||||||
|
job: group[0].job,
|
||||||
|
children: group,
|
||||||
|
size: group.reduce((acc, tree) => acc + tree.size, 0),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize grid of cells
|
||||||
|
const rows: (MerkleCell | null)[][] = [];
|
||||||
|
for (let i = 0; i < Object.keys(jobs).length; i++) {
|
||||||
|
const row: (MerkleCell | null)[] = [];
|
||||||
|
for (let j = 0; j <= numBranches; j++) {
|
||||||
|
row.push(null);
|
||||||
|
}
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill in the cells
|
||||||
|
let colTrees = [trees.sort((a, b) => {
|
||||||
|
if (a.size !== b.size) {
|
||||||
|
return b.size - a.size;
|
||||||
|
}
|
||||||
|
return a.job.localeCompare(b.job);
|
||||||
|
})];
|
||||||
|
for (let col = 0; col <= numBranches; col++) {
|
||||||
|
let row = 0;
|
||||||
|
const nextTrees: MerkleTree[][] = [];
|
||||||
|
for (let g = 0; g < colTrees.length; g++) {
|
||||||
|
for (let t = 0; t < colTrees[g].length; t++) {
|
||||||
|
const tree = colTrees[g][t];
|
||||||
|
const isFirstTree = (t === 0);
|
||||||
|
const isLastTree = (t === colTrees[g].length - 1);
|
||||||
|
for (let i = 0; i < tree.size; i++) {
|
||||||
|
const isFirstCell = (i === 0);
|
||||||
|
const isLeaf = (col === numBranches);
|
||||||
|
rows[row][col] = {
|
||||||
|
hash: tree.hash,
|
||||||
|
job: isLeaf ? jobs[tree.job] : undefined,
|
||||||
|
type: 'leaf',
|
||||||
|
};
|
||||||
|
if (col > 0) {
|
||||||
|
rows[row][col - 1].type = getCellType(isFirstCell, isFirstTree, isLastTree);
|
||||||
|
}
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
if (tree.children) {
|
||||||
|
nextTrees.push(tree.children.sort((a, b) => {
|
||||||
|
if (a.size !== b.size) {
|
||||||
|
return b.size - a.size;
|
||||||
|
}
|
||||||
|
return a.job.localeCompare(b.job);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colTrees = nextTrees;
|
||||||
|
}
|
||||||
|
return rows.map(row => ({
|
||||||
|
job: row[row.length - 1].job,
|
||||||
|
merkleCells: row.slice(0, -1),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeToClass(type: MerkleCellType): string {
|
||||||
|
return {
|
||||||
|
' ': 'empty',
|
||||||
|
'┬': 'branch-top',
|
||||||
|
'├': 'branch-mid',
|
||||||
|
'└': 'branch-end',
|
||||||
|
'│': 'vertical',
|
||||||
|
'─': 'horizontal',
|
||||||
|
'leaf': 'leaf'
|
||||||
|
}[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseHash(hash: string) {
|
||||||
|
return hash.match(/../g).reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.websocketService.stopTrackStratum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCellType(isFirstCell, isFirstTree, isLastTree): MerkleCellType {
|
||||||
|
if (isFirstCell) {
|
||||||
|
if (isFirstTree) {
|
||||||
|
if (isLastTree) {
|
||||||
|
return '─';
|
||||||
|
} else {
|
||||||
|
return '┬';
|
||||||
|
}
|
||||||
|
} else if (isLastTree) {
|
||||||
|
return '└';
|
||||||
|
} else {
|
||||||
|
return '├';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isLastTree) {
|
||||||
|
return ' ';
|
||||||
|
} else {
|
||||||
|
return '│';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,47 @@
|
|||||||
<ng-container [ngSwitch]="name">
|
<ng-container [ngSwitch]="name">
|
||||||
|
<ng-container *ngSwitchCase="'VISA'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 576 512" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
|
||||||
|
<path d="M470.1 231.3s7.6 37.2 9.3 45H446c3.3-8.9 16-43.5 16-43.5-.2 .3 3.3-9.1 5.3-14.9l2.8 13.4zM576 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h480c26.5 0 48 21.5 48 48zM152.5 331.2L215.7 176h-42.5l-39.3 106-4.3-21.5-14-71.4c-2.3-9.9-9.4-12.7-18.2-13.1H32.7l-.7 3.1c15.8 4 29.9 9.8 42.2 17.1l35.8 135h42.5zm94.4 .2L272.1 176h-40.2l-25.1 155.4h40.1zm139.9-50.8c.2-17.7-10.6-31.2-33.7-42.3-14.1-7.1-22.7-11.9-22.7-19.2 .2-6.6 7.3-13.4 23.1-13.4 13.1-.3 22.7 2.8 29.9 5.9l3.6 1.7 5.5-33.6c-7.9-3.1-20.5-6.6-36-6.6-39.7 0-67.6 21.2-67.8 51.4-.3 22.3 20 34.7 35.2 42.2 15.5 7.6 20.8 12.6 20.8 19.3-.2 10.4-12.6 15.2-24.1 15.2-16 0-24.6-2.5-37.7-8.3l-5.3-2.5-5.6 34.9c9.4 4.3 26.8 8.1 44.8 8.3 42.2 .1 69.7-20.8 70-53zM528 331.4L495.6 176h-31.1c-9.6 0-16.9 2.8-21 12.9l-59.7 142.5H426s6.9-19.2 8.4-23.3H486c1.2 5.5 4.8 23.3 4.8 23.3H528z"/>
|
||||||
|
</svg>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'MASTERCARD'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 576 512" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M482.9 410.3c0 6.8-4.6 11.7-11.2 11.7-6.8 0-11.2-5.2-11.2-11.7 0-6.5 4.4-11.7 11.2-11.7 6.6 0 11.2 5.2 11.2 11.7zm-310.8-11.7c-7.1 0-11.2 5.2-11.2 11.7 0 6.5 4.1 11.7 11.2 11.7 6.5 0 10.9-4.9 10.9-11.7-.1-6.5-4.4-11.7-10.9-11.7zm117.5-.3c-5.4 0-8.7 3.5-9.5 8.7h19.1c-.9-5.7-4.4-8.7-9.6-8.7zm107.8 .3c-6.8 0-10.9 5.2-10.9 11.7 0 6.5 4.1 11.7 10.9 11.7 6.8 0 11.2-4.9 11.2-11.7 0-6.5-4.4-11.7-11.2-11.7zm105.9 26.1c0 .3 .3 .5 .3 1.1 0 .3-.3 .5-.3 1.1-.3 .3-.3 .5-.5 .8-.3 .3-.5 .5-1.1 .5-.3 .3-.5 .3-1.1 .3-.3 0-.5 0-1.1-.3-.3 0-.5-.3-.8-.5-.3-.3-.5-.5-.5-.8-.3-.5-.3-.8-.3-1.1 0-.5 0-.8 .3-1.1 0-.5 .3-.8 .5-1.1 .3-.3 .5-.3 .8-.5 .5-.3 .8-.3 1.1-.3 .5 0 .8 0 1.1 .3 .5 .3 .8 .3 1.1 .5s.2 .6 .5 1.1zm-2.2 1.4c.5 0 .5-.3 .8-.3 .3-.3 .3-.5 .3-.8 0-.3 0-.5-.3-.8-.3 0-.5-.3-1.1-.3h-1.6v3.5h.8V426h.3l1.1 1.4h.8l-1.1-1.3zM576 81v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V81c0-26.5 21.5-48 48-48h480c26.5 0 48 21.5 48 48zM64 220.6c0 76.5 62.1 138.5 138.5 138.5 27.2 0 53.9-8.2 76.5-23.1-72.9-59.3-72.4-171.2 0-230.5-22.6-15-49.3-23.1-76.5-23.1-76.4-.1-138.5 62-138.5 138.2zm224 108.8c70.5-55 70.2-162.2 0-217.5-70.2 55.3-70.5 162.6 0 217.5zm-142.3 76.3c0-8.7-5.7-14.4-14.7-14.7-4.6 0-9.5 1.4-12.8 6.5-2.4-4.1-6.5-6.5-12.2-6.5-3.8 0-7.6 1.4-10.6 5.4V392h-8.2v36.7h8.2c0-18.9-2.5-30.2 9-30.2 10.2 0 8.2 10.2 8.2 30.2h7.9c0-18.3-2.5-30.2 9-30.2 10.2 0 8.2 10 8.2 30.2h8.2v-23zm44.9-13.7h-7.9v4.4c-2.7-3.3-6.5-5.4-11.7-5.4-10.3 0-18.2 8.2-18.2 19.3 0 11.2 7.9 19.3 18.2 19.3 5.2 0 9-1.9 11.7-5.4v4.6h7.9V392zm40.5 25.6c0-15-22.9-8.2-22.9-15.2 0-5.7 11.9-4.8 18.5-1.1l3.3-6.5c-9.4-6.1-30.2-6-30.2 8.2 0 14.3 22.9 8.3 22.9 15 0 6.3-13.5 5.8-20.7 .8l-3.5 6.3c11.2 7.6 32.6 6 32.6-7.5zm35.4 9.3l-2.2-6.8c-3.8 2.1-12.2 4.4-12.2-4.1v-16.6h13.1V392h-13.1v-11.2h-8.2V392h-7.6v7.3h7.6V416c0 17.6 17.3 14.4 22.6 10.9zm13.3-13.4h27.5c0-16.2-7.4-22.6-17.4-22.6-10.6 0-18.2 7.9-18.2 19.3 0 20.5 22.6 23.9 33.8 14.2l-3.8-6c-7.8 6.4-19.6 5.8-21.9-4.9zm59.1-21.5c-4.6-2-11.6-1.8-15.2 4.4V392h-8.2v36.7h8.2V408c0-11.6 9.5-10.1 12.8-8.4l2.4-7.6zm10.6 18.3c0-11.4 11.6-15.1 20.7-8.4l3.8-6.5c-11.6-9.1-32.7-4.1-32.7 15 0 19.8 22.4 23.8 32.7 15l-3.8-6.5c-9.2 6.5-20.7 2.6-20.7-8.6zm66.7-18.3H408v4.4c-8.3-11-29.9-4.8-29.9 13.9 0 19.2 22.4 24.7 29.9 13.9v4.6h8.2V392zm33.7 0c-2.4-1.2-11-2.9-15.2 4.4V392h-7.9v36.7h7.9V408c0-11 9-10.3 12.8-8.4l2.4-7.6zm40.3-14.9h-7.9v19.3c-8.2-10.9-29.9-5.1-29.9 13.9 0 19.4 22.5 24.6 29.9 13.9v4.6h7.9v-51.7zm7.6-75.1v4.6h.8V302h1.9v-.8h-4.6v.8h1.9zm6.6 123.8c0-.5 0-1.1-.3-1.6-.3-.3-.5-.8-.8-1.1-.3-.3-.8-.5-1.1-.8-.5 0-1.1-.3-1.6-.3-.3 0-.8 .3-1.4 .3-.5 .3-.8 .5-1.1 .8-.5 .3-.8 .8-.8 1.1-.3 .5-.3 1.1-.3 1.6 0 .3 0 .8 .3 1.4 0 .3 .3 .8 .8 1.1 .3 .3 .5 .5 1.1 .8 .5 .3 1.1 .3 1.4 .3 .5 0 1.1 0 1.6-.3 .3-.3 .8-.5 1.1-.8 .3-.3 .5-.8 .8-1.1 .3-.6 .3-1.1 .3-1.4zm3.2-124.7h-1.4l-1.6 3.5-1.6-3.5h-1.4v5.4h.8v-4.1l1.6 3.5h1.1l1.4-3.5v4.1h1.1v-5.4zm4.4-80.5c0-76.2-62.1-138.3-138.5-138.3-27.2 0-53.9 8.2-76.5 23.1 72.1 59.3 73.2 171.5 0 230.5 22.6 15 49.5 23.1 76.5 23.1 76.4 .1 138.5-61.9 138.5-138.4z"/>
|
||||||
|
</svg>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'JCB'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 576 512" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M431.5 244.3V212c41.2 0 38.5 .2 38.5 .2 7.3 1.3 13.3 7.3 13.3 16 0 8.8-6 14.5-13.3 15.8-1.2 .4-3.3 .3-38.5 .3zm42.8 20.2c-2.8-.7-3.3-.5-42.8-.5v35c39.6 0 40 .2 42.8-.5 7.5-1.5 13.5-8 13.5-17 0-8.7-6-15.5-13.5-17zM576 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h480c26.5 0 48 21.5 48 48zM182 192.3h-57c0 67.1 10.7 109.7-35.8 109.7-19.5 0-38.8-5.7-57.2-14.8v28c30 8.3 68 8.3 68 8.3 97.9 0 82-47.7 82-131.2zm178.5 4.5c-63.4-16-165-14.9-165 59.3 0 77.1 108.2 73.6 165 59.2V287C312.9 311.7 253 309 253 256s59.8-55.6 107.5-31.2v-28zM544 286.5c0-18.5-16.5-30.5-38-32v-.8c19.5-2.7 30.3-15.5 30.3-30.2 0-19-15.7-30-37-31 0 0 6.3-.3-120.3-.3v127.5h122.7c24.3 .1 42.3-12.9 42.3-33.2z"/>
|
||||||
|
</svg>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'DISCOVER'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 576 512" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M520.4 196.1c0-7.9-5.5-12.1-15.6-12.1h-4.9v24.9h4.7c10.3 0 15.8-4.4 15.8-12.8zM528 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h480c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-44.1 138.9c22.6 0 52.9-4.1 52.9 24.4 0 12.6-6.6 20.7-18.7 23.2l25.8 34.4h-19.6l-22.2-32.8h-2.2v32.8h-16zm-55.9 .1h45.3v14H444v18.2h28.3V217H444v22.2h29.3V253H428zm-68.7 0l21.9 55.2 22.2-55.2h17.5l-35.5 84.2h-8.6l-35-84.2zm-55.9-3c24.7 0 44.6 20 44.6 44.6 0 24.7-20 44.6-44.6 44.6-24.7 0-44.6-20-44.6-44.6 0-24.7 20-44.6 44.6-44.6zm-49.3 6.1v19c-20.1-20.1-46.8-4.7-46.8 19 0 25 27.5 38.5 46.8 19.2v19c-29.7 14.3-63.3-5.7-63.3-38.2 0-31.2 33.1-53 63.3-38zm-97.2 66.3c11.4 0 22.4-15.3-3.3-24.4-15-5.5-20.2-11.4-20.2-22.7 0-23.2 30.6-31.4 49.7-14.3l-8.4 10.8c-10.4-11.6-24.9-6.2-24.9 2.5 0 4.4 2.7 6.9 12.3 10.3 18.2 6.6 23.6 12.5 23.6 25.6 0 29.5-38.8 37.4-56.6 11.3l10.3-9.9c3.7 7.1 9.9 10.8 17.5 10.8zM55.4 253H32v-82h23.4c26.1 0 44.1 17 44.1 41.1 0 18.5-13.2 40.9-44.1 40.9zm67.5 0h-16v-82h16zM544 433c0 8.2-6.8 15-15 15H128c189.6-35.6 382.7-139.2 416-160zM74.1 191.6c-5.2-4.9-11.6-6.6-21.9-6.6H48v54.2h4.2c10.3 0 17-2 21.9-6.4 5.7-5.2 8.9-12.8 8.9-20.7s-3.2-15.5-8.9-20.5z"/>
|
||||||
|
</svg>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'DISCOVER_DINERS'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 576 512" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M239.7 79.9c-96.9 0-175.8 78.6-175.8 175.8 0 96.9 78.9 175.8 175.8 175.8 97.2 0 175.8-78.9 175.8-175.8 0-97.2-78.6-175.8-175.8-175.8zm-39.9 279.6c-41.7-15.9-71.4-56.4-71.4-103.8s29.7-87.9 71.4-104.1v207.9zm79.8 .3V151.6c41.7 16.2 71.4 56.7 71.4 104.1s-29.7 87.9-71.4 104.1zM528 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h480c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM329.7 448h-90.3c-106.2 0-193.8-85.5-193.8-190.2C45.6 143.2 133.2 64 239.4 64h90.3c105 0 200.7 79.2 200.7 193.8 0 104.7-95.7 190.2-200.7 190.2z"/>
|
||||||
|
</svg>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'AMERICAN_EXPRESS'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 576 512" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M0 432c0 26.5 21.5 48 48 48H528c26.5 0 48-21.5 48-48v-1.1H514.3l-31.9-35.1-31.9 35.1H246.8V267.1H181L262.7 82.4h78.6l28.1 63.2V82.4h97.2L483.5 130l17-47.6H576V80c0-26.5-21.5-48-48-48H48C21.5 32 0 53.5 0 80V432zm440.4-21.7L482.6 364l42 46.3H576l-68-72.1 68-72.1H525.4l-42 46.7-41.5-46.7H390.5L458 338.6l-67.4 71.6V377.1h-83V354.9h80.9V322.6H307.6V300.2h83V267.1h-122V410.3H440.4zm96.3-72L576 380.2V296.9l-39.3 41.4zm-36.3-92l36.9-100.6V246.3H576V103H515.8l-32.2 89.3L451.7 103H390.5V246.1L327.3 103H276.1L213.7 246.3h43l11.9-28.7h65.9l12 28.7h82.7V146L466 246.3h34.4zM282 185.4l19.5-46.9 19.4 46.9H282z"/>
|
||||||
|
</svg>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'OTHER_BRAND'">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.width]="width" [attr.height]="height" viewBox="0 0 576 512" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M512 80c8.8 0 16 7.2 16 16l0 32L48 128l0-32c0-8.8 7.2-16 16-16l448 0zm16 144l0 192c0 8.8-7.2 16-16 16L64 432c-8.8 0-16-7.2-16-16l0-192 480 0zM64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l448 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm56 304c-13.3 0-24 10.7-24 24s10.7 24 24 24l48 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-48 0zm128 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l112 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-112 0z"/>
|
||||||
|
</svg>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngSwitchCase="'officialMempoolSpace'">
|
<ng-container *ngSwitchCase="'officialMempoolSpace'">
|
||||||
<svg [class]="class" [style]="style" [attr.width]="width" [attr.height]="height" [attr.viewBox]="viewBox" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg [class]="class" [style]="style" [attr.width]="width" [attr.height]="height" [attr.viewBox]="viewBox" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M163.658 113.263C161.089 113.263 158.992 111.146 158.992 108.535C158.992 105.966 161.048 103.951 163.658 103.951C166.269 103.951 168.325 105.966 168.325 108.535C168.325 111.125 166.228 113.263 163.658 113.263Z" fill="#9857FF"/>
|
<path d="M163.658 113.263C161.089 113.263 158.992 111.146 158.992 108.535C158.992 105.966 161.048 103.951 163.658 103.951C166.269 103.951 168.325 105.966 168.325 108.535C168.325 111.125 166.228 113.263 163.658 113.263Z" fill="#9857FF"/>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</a>
|
</a>
|
||||||
} @else if (enterpriseInfo?.img || enterpriseInfo?.imageMd5) {
|
} @else if (enterpriseInfo?.img || enterpriseInfo?.imageMd5) {
|
||||||
<a [routerLink]="['/' | relativeUrl]">
|
<a [routerLink]="['/' | relativeUrl]">
|
||||||
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + enterpriseInfo.name + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + enterpriseInfo.name + '/logo'" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
</a>
|
</a>
|
||||||
<div class="vertical-line"></div>
|
<div class="vertical-line"></div>
|
||||||
}
|
}
|
||||||
@ -124,7 +124,6 @@
|
|||||||
<ng-container *ngIf="(ETA$ | async) as eta;">
|
<ng-container *ngIf="(ETA$ | async) as eta;">
|
||||||
<app-accelerate-checkout
|
<app-accelerate-checkout
|
||||||
*ngIf="(da$ | async) as da;"
|
*ngIf="(da$ | async) as da;"
|
||||||
[cashappEnabled]="cashappEligible"
|
|
||||||
[advancedEnabled]="false"
|
[advancedEnabled]="false"
|
||||||
[forceMobile]="true"
|
[forceMobile]="true"
|
||||||
[tx]="tx"
|
[tx]="tx"
|
||||||
|
@ -756,10 +756,6 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get cashappEligible(): boolean {
|
|
||||||
return this.mempoolPosition?.block > 0 && this.tx.weight < 4000;
|
|
||||||
}
|
|
||||||
|
|
||||||
get showAccelerationSummary(): boolean {
|
get showAccelerationSummary(): boolean {
|
||||||
return (
|
return (
|
||||||
this.tx
|
this.tx
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
[height]="tx?.status?.block_height"
|
[height]="tx?.status?.block_height"
|
||||||
[replaced]="replaced"
|
[replaced]="replaced"
|
||||||
[removed]="this.rbfInfo?.mined && !this.tx?.status?.confirmed"
|
[removed]="this.rbfInfo?.mined && !this.tx?.status?.confirmed"
|
||||||
|
[cached]="isCached"
|
||||||
></app-confirmations>
|
></app-confirmations>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -138,7 +139,6 @@
|
|||||||
|
|
||||||
<app-accelerate-checkout
|
<app-accelerate-checkout
|
||||||
*ngIf="(da$ | async) as da;"
|
*ngIf="(da$ | async) as da;"
|
||||||
[cashappEnabled]="cashappEligible"
|
|
||||||
[advancedEnabled]="true"
|
[advancedEnabled]="true"
|
||||||
[tx]="tx"
|
[tx]="tx"
|
||||||
[accelerating]="isAcceleration"
|
[accelerating]="isAcceleration"
|
||||||
|
@ -156,7 +156,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
showAccelerationDetails = false;
|
showAccelerationDetails = false;
|
||||||
hasAccelerationDetails = false;
|
hasAccelerationDetails = false;
|
||||||
scrollIntoAccelPreview = false;
|
scrollIntoAccelPreview = false;
|
||||||
cashappEligible = false;
|
|
||||||
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
|
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
|
||||||
isMempoolSpaceBuild = this.stateService.isMempoolSpaceBuild;
|
isMempoolSpaceBuild = this.stateService.isMempoolSpaceBuild;
|
||||||
|
|
||||||
@ -240,7 +239,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
retry({ count: 2, delay: 2000 }),
|
retry({ count: 2, delay: 2000 }),
|
||||||
// Try again until we either get a valid response, or the transaction is confirmed
|
// Try again until we either get a valid response, or the transaction is confirmed
|
||||||
repeat({ delay: 2000 }),
|
repeat({ delay: 2000 }),
|
||||||
filter((transactionTimes) => transactionTimes?.length && transactionTimes[0] > 0 && !this.tx.status?.confirmed),
|
filter((transactionTimes) => transactionTimes?.[0] > 0 || this.tx.status?.confirmed),
|
||||||
take(1),
|
take(1),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@ -528,9 +527,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.miningStats = stats;
|
this.miningStats = stats;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (txPosition.position?.block > 0 && this.tx.weight < 4000) {
|
|
||||||
this.cashappEligible = true;
|
|
||||||
}
|
|
||||||
if (!this.gotInitialPosition && txPosition.position?.block === 0 && txPosition.position?.vsize < 750_000) {
|
if (!this.gotInitialPosition && txPosition.position?.block === 0 && txPosition.position?.vsize < 750_000) {
|
||||||
this.accelerationFlowCompleted = true;
|
this.accelerationFlowCompleted = true;
|
||||||
}
|
}
|
||||||
@ -1036,7 +1032,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.showAccelerationDetails = false;
|
this.showAccelerationDetails = false;
|
||||||
this.accelerationFlowCompleted = false;
|
this.accelerationFlowCompleted = false;
|
||||||
this.accelerationInfo = null;
|
this.accelerationInfo = null;
|
||||||
this.cashappEligible = false;
|
|
||||||
this.txInBlockIndex = null;
|
this.txInBlockIndex = null;
|
||||||
this.mempoolPosition = null;
|
this.mempoolPosition = null;
|
||||||
this.pool = null;
|
this.pool = null;
|
||||||
|
@ -202,12 +202,12 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
for (const address of this.addresses) {
|
for (const address of this.addresses) {
|
||||||
switch (address.length) {
|
switch (address.length) {
|
||||||
case 130: {
|
case 130: {
|
||||||
if (v.scriptpubkey === '21' + address + 'ac') {
|
if (v.scriptpubkey === '41' + address + 'ac') {
|
||||||
return v.value;
|
return v.value;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 66: {
|
case 66: {
|
||||||
if (v.scriptpubkey === '41' + address + 'ac') {
|
if (v.scriptpubkey === '21' + address + 'ac') {
|
||||||
return v.value;
|
return v.value;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
@ -224,12 +224,12 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
for (const address of this.addresses) {
|
for (const address of this.addresses) {
|
||||||
switch (address.length) {
|
switch (address.length) {
|
||||||
case 130: {
|
case 130: {
|
||||||
if (v.prevout?.scriptpubkey === '21' + address + 'ac') {
|
if (v.prevout?.scriptpubkey === '41' + address + 'ac') {
|
||||||
return v.prevout?.value;
|
return v.prevout?.value;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 66: {
|
case 66: {
|
||||||
if (v.prevout?.scriptpubkey === '41' + address + 'ac') {
|
if (v.prevout?.scriptpubkey === '21' + address + 'ac') {
|
||||||
return v.prevout?.value;
|
return v.prevout?.value;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<a href="#" (click)="twitterLogin()"
|
<a href="#" (click)="twitterLogin()"
|
||||||
[class]="(disabled ? 'disabled': '') + (customClass ? customClass : 'w-100 btn mt-1 d-flex justify-content-center align-items-center')"
|
[class]="(disabled ? 'disabled': '') + (customClass ? customClass : 'w-100 btn mt-1 d-flex justify-content-center align-items-center')"
|
||||||
style="background-color: #1DA1F2" [style]="width ? 'width: ' + width : ''">
|
style="background-color: rgb(31, 35, 40)" [style]="width ? 'width: ' + width : ''">
|
||||||
<img src="./resources/twitter.svg" height="25" style="padding: 2px" [alt]="buttonString + ' with Twitter'" />
|
|
||||||
<span class="ml-2 text-light align-middle">{{ buttonString }}</span>
|
<span class="ml-2 text-light align-middle">{{ buttonString }}</span>
|
||||||
|
<img src="./resources/x.svg" height="25" style="padding: 2px; padding-left: 5px" [alt]="buttonString + ' with Twitter'" />
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="container-xl" [class.liquid-address]="network === 'liquid' || network === 'liquidtestnet'">
|
<div class="container-xl" [class.liquid-address]="network === 'liquid' || network === 'liquidtestnet'">
|
||||||
<div class="title-address">
|
<div class="title-address">
|
||||||
<h1 i18n="shared.wallet">Wallet</h1>
|
<h1>{{ walletName }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
@ -74,6 +74,36 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="title-tx">
|
||||||
|
<h2 class="text-left" i18n="address.transactions">Transactions</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" [addresses]="addressStrings" (loadMore)="loadMore()"></app-transactions-list>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<ng-template [ngIf]="isLoadingTransactions">
|
||||||
|
<div class="header-bg box">
|
||||||
|
<div class="row" style="height: 107px;">
|
||||||
|
<div class="col-sm">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</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>
|
||||||
|
|
||||||
|
|
||||||
<ng-template #loadingTemplate>
|
<ng-template #loadingTemplate>
|
||||||
|
|
||||||
<div class="box" *ngIf="!error; else errorTemplate">
|
<div class="box" *ngIf="!error; else errorTemplate">
|
||||||
|
@ -9,6 +9,8 @@ import { of, Observable, Subscription } from 'rxjs';
|
|||||||
import { SeoService } from '@app/services/seo.service';
|
import { SeoService } from '@app/services/seo.service';
|
||||||
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||||
import { WalletAddress } from '@interfaces/node-api.interface';
|
import { WalletAddress } from '@interfaces/node-api.interface';
|
||||||
|
import { ElectrsApiService } from '@app/services/electrs-api.service';
|
||||||
|
import { AudioService } from '@app/services/audio.service';
|
||||||
|
|
||||||
class WalletStats implements ChainStats {
|
class WalletStats implements ChainStats {
|
||||||
addresses: string[];
|
addresses: string[];
|
||||||
@ -24,6 +26,7 @@ class WalletStats implements ChainStats {
|
|||||||
acc.funded_txo_sum += stat.funded_txo_sum;
|
acc.funded_txo_sum += stat.funded_txo_sum;
|
||||||
acc.spent_txo_count += stat.spent_txo_count;
|
acc.spent_txo_count += stat.spent_txo_count;
|
||||||
acc.spent_txo_sum += stat.spent_txo_sum;
|
acc.spent_txo_sum += stat.spent_txo_sum;
|
||||||
|
acc.tx_count += stat.tx_count;
|
||||||
return acc;
|
return acc;
|
||||||
}, {
|
}, {
|
||||||
funded_txo_count: 0,
|
funded_txo_count: 0,
|
||||||
@ -109,12 +112,17 @@ export class WalletComponent implements OnInit, OnDestroy {
|
|||||||
addressStrings: string[] = [];
|
addressStrings: string[] = [];
|
||||||
walletName: string;
|
walletName: string;
|
||||||
isLoadingWallet = true;
|
isLoadingWallet = true;
|
||||||
|
isLoadingTransactions = true;
|
||||||
|
transactions: Transaction[];
|
||||||
|
totalTransactionCount: number;
|
||||||
|
retryLoadMore = false;
|
||||||
wallet$: Observable<Record<string, WalletAddress>>;
|
wallet$: Observable<Record<string, WalletAddress>>;
|
||||||
walletAddresses$: Observable<Record<string, Address>>;
|
walletAddresses$: Observable<Record<string, Address>>;
|
||||||
walletSummary$: Observable<AddressTxSummary[]>;
|
walletSummary$: Observable<AddressTxSummary[]>;
|
||||||
walletStats$: Observable<WalletStats>;
|
walletStats$: Observable<WalletStats>;
|
||||||
error: any;
|
error: any;
|
||||||
walletSubscription: Subscription;
|
walletSubscription: Subscription;
|
||||||
|
transactionSubscription: Subscription;
|
||||||
|
|
||||||
collapseAddresses: boolean = true;
|
collapseAddresses: boolean = true;
|
||||||
|
|
||||||
@ -129,6 +137,8 @@ export class WalletComponent implements OnInit, OnDestroy {
|
|||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
|
private electrsApiService: ElectrsApiService,
|
||||||
|
private audioService: AudioService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@ -172,6 +182,21 @@ export class WalletComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
switchMap(initial => this.stateService.walletTransactions$.pipe(
|
switchMap(initial => this.stateService.walletTransactions$.pipe(
|
||||||
startWith(null),
|
startWith(null),
|
||||||
|
tap((transactions) => {
|
||||||
|
if (!transactions?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const transaction of transactions) {
|
||||||
|
const tx = this.transactions.find((t) => t.txid === transaction.txid);
|
||||||
|
if (tx) {
|
||||||
|
tx.status = transaction.status;
|
||||||
|
} else {
|
||||||
|
this.transactions.unshift(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.transactions = this.transactions.slice();
|
||||||
|
this.audioService.playSound('magic');
|
||||||
|
}),
|
||||||
scan((wallet, walletTransactions) => {
|
scan((wallet, walletTransactions) => {
|
||||||
for (const tx of (walletTransactions || [])) {
|
for (const tx of (walletTransactions || [])) {
|
||||||
const funded: Record<string, number> = {};
|
const funded: Record<string, number> = {};
|
||||||
@ -267,8 +292,57 @@ export class WalletComponent implements OnInit, OnDestroy {
|
|||||||
return stats;
|
return stats;
|
||||||
}, walletStats),
|
}, walletStats),
|
||||||
);
|
);
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.transactionSubscription = this.wallet$.pipe(
|
||||||
|
switchMap(wallet => {
|
||||||
|
const addresses = Object.keys(wallet).map(addr => this.normalizeAddress(addr));
|
||||||
|
return this.electrsApiService.getAddressesTransactions$(addresses);
|
||||||
|
}),
|
||||||
|
map(transactions => {
|
||||||
|
// only confirmed transactions supported for now
|
||||||
|
return transactions.filter(tx => tx.status.confirmed).sort((a, b) => b.status.block_height - a.status.block_height);
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
console.log(error);
|
||||||
|
this.error = error;
|
||||||
|
this.seoService.logSoft404();
|
||||||
|
this.isLoadingWallet = false;
|
||||||
|
return of([]);
|
||||||
|
})
|
||||||
|
).subscribe((transactions: Transaction[] | null) => {
|
||||||
|
if (!transactions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.transactions = transactions;
|
||||||
|
this.isLoadingTransactions = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMore(): void {
|
||||||
|
if (this.isLoadingTransactions || this.fullyLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isLoadingTransactions = true;
|
||||||
|
this.retryLoadMore = false;
|
||||||
|
this.electrsApiService.getAddressesTransactions$(this.addressStrings, this.transactions[this.transactions.length - 1].txid)
|
||||||
|
.subscribe((transactions: Transaction[]) => {
|
||||||
|
if (transactions && transactions.length) {
|
||||||
|
this.transactions = this.transactions.concat(transactions.sort((a, b) => b.status.block_height - a.status.block_height));
|
||||||
|
} else {
|
||||||
|
this.fullyLoaded = true;
|
||||||
|
}
|
||||||
|
this.isLoadingTransactions = false;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.isLoadingTransactions = false;
|
||||||
|
this.retryLoadMore = true;
|
||||||
|
// In the unlikely event of the txid wasn't found in the mempool anymore and we must reload the page.
|
||||||
|
if (error.status === 422) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deduplicateWalletTransactions(walletTransactions: AddressTxSummary[]): AddressTxSummary[] {
|
deduplicateWalletTransactions(walletTransactions: AddressTxSummary[]): AddressTxSummary[] {
|
||||||
@ -299,5 +373,6 @@ export class WalletComponent implements OnInit, OnDestroy {
|
|||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.websocketService.stopTrackingWallet();
|
this.websocketService.stopTrackingWallet();
|
||||||
this.walletSubscription.unsubscribe();
|
this.walletSubscription.unsubscribe();
|
||||||
|
this.transactionSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
import { Env, StateService } from '@app/services/state.service';
|
import { Env, StateService } from '@app/services/state.service';
|
||||||
import { restApiDocsData } from '@app/docs/api-docs/api-docs-data';
|
import { restApiDocsData, wsApiDocsData } from '@app/docs/api-docs/api-docs-data';
|
||||||
import { faqData } from '@app/docs/api-docs/api-docs-data';
|
import { faqData } from '@app/docs/api-docs/api-docs-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -28,6 +28,8 @@ export class ApiDocsNavComponent implements OnInit {
|
|||||||
this.auditEnabled = this.env.AUDIT;
|
this.auditEnabled = this.env.AUDIT;
|
||||||
if (this.whichTab === 'rest') {
|
if (this.whichTab === 'rest') {
|
||||||
this.tabData = restApiDocsData;
|
this.tabData = restApiDocsData;
|
||||||
|
} else if (this.whichTab === 'websocket') {
|
||||||
|
this.tabData = wsApiDocsData;
|
||||||
} else if (this.whichTab === 'faq') {
|
} else if (this.whichTab === 'faq') {
|
||||||
this.tabData = faqData;
|
this.tabData = faqData;
|
||||||
}
|
}
|
||||||
|
@ -108,18 +108,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="websocketAPI" *ngIf="( whichTab === 'websocket' )">
|
<div id="websocketAPI" *ngIf="whichTab === 'websocket'">
|
||||||
<div class="api-category">
|
|
||||||
<div class="websocket">
|
<div id="doc-nav-desktop" class="hide-on-mobile" [ngClass]="desktopDocsNavPosition">
|
||||||
<div class="endpoint">
|
<app-api-docs-nav (navLinkClickEvent)="anchorLinkClick( $event )" [network]="{ val: network$ | async }" [whichTab]="whichTab"></app-api-docs-nav>
|
||||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
</div>
|
||||||
{{ wrapUrl(network.val, wsDocs, true) }}
|
|
||||||
|
<div class="doc-content">
|
||||||
|
|
||||||
|
<div id="enterprise-cta-mobile" *ngIf="officialMempoolInstance && showMobileEnterpriseUpsell">
|
||||||
|
<p>Get higher API limits with <span class="no-line-break">Mempool Enterprise®</span></p>
|
||||||
|
<div class="button-group">
|
||||||
|
<a class="btn btn-small btn-secondary" (click)="showMobileEnterpriseUpsell = false">No Thanks</a>
|
||||||
|
<a class="btn btn-small btn-purple" href="https://mempool.space/enterprise">More Info <fa-icon [icon]="['fas', 'angle-right']" [styles]="{'font-size': '12px'}"></fa-icon></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="description">
|
</div>
|
||||||
<div class="subtitle" i18n>Description</div>
|
|
||||||
<div i18n="api-docs.websocket.websocket">Default push: <code>{{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }}</code> to express what you want pushed. Available: <code>blocks</code>, <code>mempool-blocks</code>, <code>live-2h-chart</code>, and <code>stats</code>.<br><br>Push transactions related to address: <code>{{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }}</code> to receive all new transactions containing that address as input or output. Returns an array of transactions. <code>address-transactions</code> for new mempool transactions, and <code>block-transactions</code> for new block confirmed transactions.</div>
|
<p class="doc-welcome-note">Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title-websocket">Websocket service</ng-container> running at {{ websocketUrl(network.val) }}.</p>
|
||||||
|
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that usage limits apply to our WebSocket API. Consider an <a href="https://mempool.space/enterprise">enterprise sponsorship</a> if you need higher API limits, such as higher tracking limits.</p>
|
||||||
|
|
||||||
|
<div class="doc-item-container" *ngFor="let item of wsDocs">
|
||||||
|
<div *ngIf="!item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance )">
|
||||||
|
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
|
||||||
|
<div *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" class="endpoint-container" id="{{ item.fragment }}">
|
||||||
|
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick({event: $event, fragment: item.fragment})">{{ item.title }} <span>{{ item.category }}</span></a>
|
||||||
|
<div class="endpoint-content">
|
||||||
|
<div class="description">
|
||||||
|
<div class="subtitle" i18n>Description</div>
|
||||||
|
<div [innerHTML]="item.description.default" i18n></div>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<div class="subtitle" i18n>Payload</div>
|
||||||
|
<pre><code [innerText]="item.payload"></code></pre>
|
||||||
|
</div>
|
||||||
|
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.default" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-code-template [method]="'websocket'" [hostname]="hostname" [code]="wsDocs" [network]="network.val" [showCodeExample]="wsDocs.showJsExamples"></app-code-template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -470,3 +470,21 @@ dd {
|
|||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: var(--bg);
|
||||||
|
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
font-size: 87.5%;
|
||||||
|
color: #f18920;
|
||||||
|
background-color: var(--bg);
|
||||||
|
padding: 30px;
|
||||||
|
code{
|
||||||
|
background-color: transparent;
|
||||||
|
white-space: break-spaces;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
@ -145,7 +145,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
|||||||
if (document.getElementById( targetId + "-tab-header" )) {
|
if (document.getElementById( targetId + "-tab-header" )) {
|
||||||
tabHeaderHeight = document.getElementById( targetId + "-tab-header" ).scrollHeight;
|
tabHeaderHeight = document.getElementById( targetId + "-tab-header" ).scrollHeight;
|
||||||
}
|
}
|
||||||
if( ( window.innerWidth <= 992 ) && ( ( this.whichTab === 'rest' ) || ( this.whichTab === 'faq' ) ) && targetId ) {
|
if( ( window.innerWidth <= 992 ) && ( ( this.whichTab === 'rest' ) || ( this.whichTab === 'faq' ) || ( this.whichTab === 'websocket' ) ) && targetId ) {
|
||||||
const endpointContainerEl = document.querySelector<HTMLElement>( "#" + targetId );
|
const endpointContainerEl = document.querySelector<HTMLElement>( "#" + targetId );
|
||||||
const endpointContentEl = document.querySelector<HTMLElement>( "#" + targetId + " .endpoint-content" );
|
const endpointContentEl = document.querySelector<HTMLElement>( "#" + targetId + " .endpoint-content" );
|
||||||
const endPointContentElHeight = endpointContentEl.clientHeight;
|
const endPointContentElHeight = endpointContentEl.clientHeight;
|
||||||
@ -207,13 +207,29 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
|||||||
text = text.replace('%{' + indexNumber + '}', curlText);
|
text = text.replace('%{' + indexNumber + '}', curlText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (websocket) {
|
|
||||||
const wsHostname = this.hostname.replace('https://', 'wss://');
|
|
||||||
wsHostname.replace('http://', 'ws://');
|
|
||||||
return `${wsHostname}${curlNetwork}${text}`;
|
|
||||||
}
|
|
||||||
return `${this.hostname}${curlNetwork}${text}`;
|
return `${this.hostname}${curlNetwork}${text}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
websocketUrl(network: string) {
|
||||||
|
let curlNetwork = '';
|
||||||
|
if (this.env.BASE_MODULE === 'mempool') {
|
||||||
|
if (!['', 'mainnet'].includes(network)) {
|
||||||
|
curlNetwork = `/${network}`;
|
||||||
|
}
|
||||||
|
} else if (this.env.BASE_MODULE === 'liquid') {
|
||||||
|
if (!['', 'liquid'].includes(network)) {
|
||||||
|
curlNetwork = `/${network}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (network === this.env.ROOT_NETWORK) {
|
||||||
|
curlNetwork = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let wsHostname = this.hostname.replace('https://', 'wss://');
|
||||||
|
wsHostname = wsHostname.replace('http://', 'ws://');
|
||||||
|
return `${wsHostname}${curlNetwork}/api/v1/ws`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AddressTxSummary, Block, ChainStats, Transaction } from "./electrs.interface";
|
import { AddressTxSummary, Block, ChainStats } from "./electrs.interface";
|
||||||
|
|
||||||
export interface OptimizedMempoolStats {
|
export interface OptimizedMempoolStats {
|
||||||
added: number;
|
added: number;
|
||||||
@ -412,13 +412,13 @@ export interface Acceleration {
|
|||||||
feeDelta: number;
|
feeDelta: number;
|
||||||
blockHash: string;
|
blockHash: string;
|
||||||
blockHeight: number;
|
blockHeight: number;
|
||||||
|
|
||||||
acceleratedFeeRate?: number;
|
acceleratedFeeRate?: number;
|
||||||
boost?: number;
|
boost?: number;
|
||||||
bidBoost?: number;
|
bidBoost?: number;
|
||||||
boostCost?: number;
|
boostCost?: number;
|
||||||
boostRate?: number;
|
boostRate?: number;
|
||||||
minedByPoolUniqueId?: number;
|
minedByPoolUniqueId?: number;
|
||||||
|
canceled?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccelerationHistoryParams {
|
export interface AccelerationHistoryParams {
|
||||||
|
@ -21,6 +21,8 @@ export interface WebsocketResponse {
|
|||||||
rbfInfo?: RbfTree;
|
rbfInfo?: RbfTree;
|
||||||
rbfLatest?: RbfTree[];
|
rbfLatest?: RbfTree[];
|
||||||
rbfLatestSummary?: ReplacementInfo[];
|
rbfLatestSummary?: ReplacementInfo[];
|
||||||
|
stratumJob?: StratumJob;
|
||||||
|
stratumJobs?: Record<number, StratumJob>;
|
||||||
utxoSpent?: object;
|
utxoSpent?: object;
|
||||||
transactions?: TransactionStripped[];
|
transactions?: TransactionStripped[];
|
||||||
loadingIndicators?: ILoadingIndicators;
|
loadingIndicators?: ILoadingIndicators;
|
||||||
@ -37,6 +39,7 @@ export interface WebsocketResponse {
|
|||||||
'track-rbf-summary'?: boolean;
|
'track-rbf-summary'?: boolean;
|
||||||
'track-accelerations'?: boolean;
|
'track-accelerations'?: boolean;
|
||||||
'track-wallet'?: string;
|
'track-wallet'?: string;
|
||||||
|
'track-stratum'?: string | number;
|
||||||
'watch-mempool'?: boolean;
|
'watch-mempool'?: boolean;
|
||||||
'refresh-blocks'?: boolean;
|
'refresh-blocks'?: boolean;
|
||||||
}
|
}
|
||||||
@ -150,3 +153,24 @@ export interface HealthCheckHost {
|
|||||||
electrs?: string;
|
electrs?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StratumJob {
|
||||||
|
pool: number;
|
||||||
|
height: number;
|
||||||
|
coinbase: string;
|
||||||
|
scriptsig: string;
|
||||||
|
reward: number;
|
||||||
|
jobId: string;
|
||||||
|
extraNonce: string;
|
||||||
|
extraNonce2Size: number;
|
||||||
|
prevHash: string;
|
||||||
|
coinbase1: string;
|
||||||
|
coinbase2: string;
|
||||||
|
merkleBranches: string[];
|
||||||
|
version: string;
|
||||||
|
bits: string;
|
||||||
|
time: string;
|
||||||
|
timestamp: number;
|
||||||
|
cleanJobs: boolean;
|
||||||
|
received: number;
|
||||||
|
}
|
||||||
|
@ -10,9 +10,10 @@ import { TestTransactionsComponent } from '@components/test-transactions/test-tr
|
|||||||
import { CalculatorComponent } from '@components/calculator/calculator.component';
|
import { CalculatorComponent } from '@components/calculator/calculator.component';
|
||||||
import { BlocksList } from '@components/blocks-list/blocks-list.component';
|
import { BlocksList } from '@components/blocks-list/blocks-list.component';
|
||||||
import { RbfList } from '@components/rbf-list/rbf-list.component';
|
import { RbfList } from '@components/rbf-list/rbf-list.component';
|
||||||
|
import { StratumList } from '@components/stratum/stratum-list/stratum-list.component';
|
||||||
import { ServerHealthComponent } from '@components/server-health/server-health.component';
|
import { ServerHealthComponent } from '@components/server-health/server-health.component';
|
||||||
import { ServerStatusComponent } from '@components/server-health/server-status.component';
|
import { ServerStatusComponent } from '@components/server-health/server-status.component';
|
||||||
import { FaucetComponent } from '@components/faucet/faucet.component'
|
import { FaucetComponent } from '@components/faucet/faucet.component';
|
||||||
|
|
||||||
const browserWindow = window || {};
|
const browserWindow = window || {};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -56,6 +57,16 @@ const routes: Routes = [
|
|||||||
path: 'rbf',
|
path: 'rbf',
|
||||||
component: RbfList,
|
component: RbfList,
|
||||||
},
|
},
|
||||||
|
...(browserWindowEnv.STRATUM_ENABLED ? [{
|
||||||
|
path: 'stratum',
|
||||||
|
component: StartComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: StratumList,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}] : []),
|
||||||
{
|
{
|
||||||
path: 'terms-of-service',
|
path: 'terms-of-service',
|
||||||
loadChildren: () => import('@components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule),
|
loadChildren: () => import('@components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule),
|
||||||
|
@ -58,6 +58,7 @@ export class AuthServiceMempool {
|
|||||||
setAuth(auth: any) {
|
setAuth(auth: any) {
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
localStorage.removeItem('auth');
|
localStorage.removeItem('auth');
|
||||||
|
localStorage.removeItem('authenticatorStatus');
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem('auth', JSON.stringify(auth));
|
localStorage.setItem('auth', JSON.stringify(auth));
|
||||||
}
|
}
|
||||||
|
@ -142,12 +142,16 @@ export class ElectrsApiService {
|
|||||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs', { params });
|
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs', { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddressesTransactions$(addresses: string[], txid?: string): Observable<Transaction[]> {
|
getAddressesTransactions$(addresses: string[], txid?: string): Observable<Transaction[]> {
|
||||||
let params = new HttpParams();
|
let params = new HttpParams();
|
||||||
if (txid) {
|
if (txid) {
|
||||||
params = params.append('after_txid', txid);
|
params = params.append('after_txid', txid);
|
||||||
}
|
}
|
||||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs?addresses=${addresses.join(',')}`, { params });
|
return this.httpClient.post<Transaction[]>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs',
|
||||||
|
addresses,
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddressSummary$(address: string, txid?: string): Observable<AddressTxSummary[]> {
|
getAddressSummary$(address: string, txid?: string): Observable<AddressTxSummary[]> {
|
||||||
@ -163,7 +167,7 @@ export class ElectrsApiService {
|
|||||||
if (txid) {
|
if (txid) {
|
||||||
params = params.append('after_txid', txid);
|
params = params.append('after_txid', txid);
|
||||||
}
|
}
|
||||||
return this.httpClient.get<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs/summary?addresses=${addresses.join(',')}`, { params });
|
return this.httpClient.post<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs/summary', addresses, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
getScriptHashTransactions$(script: string, txid?: string): Observable<Transaction[]> {
|
getScriptHashTransactions$(script: string, txid?: string): Observable<Transaction[]> {
|
||||||
@ -182,7 +186,7 @@ export class ElectrsApiService {
|
|||||||
params = params.append('after_txid', txid);
|
params = params.append('after_txid', txid);
|
||||||
}
|
}
|
||||||
return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe(
|
return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe(
|
||||||
switchMap(scriptHashes => this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs?scripthashes=${scriptHashes.join(',')}`, { params })),
|
switchMap(scriptHashes => this.httpClient.post<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/scripthashes/txs', scriptHashes, { params })),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +216,7 @@ export class ElectrsApiService {
|
|||||||
params = params.append('after_txid', txid);
|
params = params.append('after_txid', txid);
|
||||||
}
|
}
|
||||||
return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe(
|
return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe(
|
||||||
switchMap(scriptHashes => this.httpClient.get<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs/summary?scripthashes=${scriptHashes.join(',')}`, { params })),
|
switchMap(scriptHashes => this.httpClient.post<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + '/api/scripthashes/txs/summary', scriptHashes, { params })),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,8 +64,8 @@ export class MiningService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get names and slugs of all pools
|
* Get names and slugs of all pools
|
||||||
*/
|
*/
|
||||||
public getPools(): Observable<any[]> {
|
public getPools(): Observable<any[]> {
|
||||||
@ -75,7 +75,6 @@ export class MiningService {
|
|||||||
return this.poolsData;
|
return this.poolsData;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Set the hashrate power of ten we want to display
|
* Set the hashrate power of ten we want to display
|
||||||
|
@ -18,7 +18,6 @@ export interface IUser {
|
|||||||
subscription_tag: string;
|
subscription_tag: string;
|
||||||
status: 'pending' | 'verified' | 'disabled';
|
status: 'pending' | 'verified' | 'disabled';
|
||||||
features: string | null;
|
features: string | null;
|
||||||
fullName: string | null;
|
|
||||||
countryCode: string | null;
|
countryCode: string | null;
|
||||||
imageMd5: string;
|
imageMd5: string;
|
||||||
ogRank: number | null;
|
ogRank: number | null;
|
||||||
@ -143,8 +142,12 @@ export class ServicesApiServices {
|
|||||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
||||||
}
|
}
|
||||||
|
|
||||||
accelerateWithGooglePay$(txInput: string, token: string, verificationToken: string, cardTag: string, referenceId: string, userApprovedUSD: number) {
|
accelerateWithGooglePay$(txInput: string, token: string, verificationToken: string, cardTag: string, referenceId: string, userApprovedUSD: number, userChallenged: boolean) {
|
||||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged });
|
||||||
|
}
|
||||||
|
|
||||||
|
accelerateWithCardOnFile$(txInput: string, token: string, verificationToken: string, referenceId: string, userApprovedUSD: number, userChallenged: boolean) {
|
||||||
|
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cardOnFile`, { txInput: txInput, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccelerations$(): Observable<Acceleration[]> {
|
getAccelerations$(): Observable<Acceleration[]> {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
||||||
import { AddressTxSummary, Transaction } from '@interfaces/electrs.interface';
|
import { Transaction } from '@interfaces/electrs.interface';
|
||||||
import { AccelerationDelta, HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockUpdate, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, isMempoolState } from '@interfaces/websocket.interface';
|
import { AccelerationDelta, HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockUpdate, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, StratumJob, isMempoolState } from '@interfaces/websocket.interface';
|
||||||
import { Acceleration, AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '@interfaces/node-api.interface';
|
import { Acceleration, AccelerationPosition, BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '@interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
@ -81,6 +81,7 @@ export interface Env {
|
|||||||
ADDITIONAL_CURRENCIES: boolean;
|
ADDITIONAL_CURRENCIES: boolean;
|
||||||
GIT_COMMIT_HASH_MEMPOOL_SPACE?: string;
|
GIT_COMMIT_HASH_MEMPOOL_SPACE?: string;
|
||||||
PACKAGE_JSON_VERSION_MEMPOOL_SPACE?: string;
|
PACKAGE_JSON_VERSION_MEMPOOL_SPACE?: string;
|
||||||
|
STRATUM_ENABLED: boolean;
|
||||||
SERVICES_API?: string;
|
SERVICES_API?: string;
|
||||||
customize?: Customization;
|
customize?: Customization;
|
||||||
PROD_DOMAINS: string[];
|
PROD_DOMAINS: string[];
|
||||||
@ -123,6 +124,7 @@ const defaultEnv: Env = {
|
|||||||
'ACCELERATOR_BUTTON': true,
|
'ACCELERATOR_BUTTON': true,
|
||||||
'PUBLIC_ACCELERATIONS': false,
|
'PUBLIC_ACCELERATIONS': false,
|
||||||
'ADDITIONAL_CURRENCIES': false,
|
'ADDITIONAL_CURRENCIES': false,
|
||||||
|
'STRATUM_ENABLED': false,
|
||||||
'SERVICES_API': 'https://mempool.space/api/v1/services',
|
'SERVICES_API': 'https://mempool.space/api/v1/services',
|
||||||
'PROD_DOMAINS': [],
|
'PROD_DOMAINS': [],
|
||||||
};
|
};
|
||||||
@ -159,6 +161,8 @@ export class StateService {
|
|||||||
liveMempoolBlockTransactions$: Observable<{ block: number, transactions: { [txid: string]: TransactionStripped} }>;
|
liveMempoolBlockTransactions$: Observable<{ block: number, transactions: { [txid: string]: TransactionStripped} }>;
|
||||||
accelerations$ = new Subject<AccelerationDelta>();
|
accelerations$ = new Subject<AccelerationDelta>();
|
||||||
liveAccelerations$: Observable<Acceleration[]>;
|
liveAccelerations$: Observable<Acceleration[]>;
|
||||||
|
stratumJobUpdate$ = new Subject<{ state: Record<string, StratumJob> } | { job: StratumJob }>();
|
||||||
|
stratumJobs$ = new BehaviorSubject<Record<string, StratumJob>>({});
|
||||||
txConfirmed$ = new Subject<[string, BlockExtended]>();
|
txConfirmed$ = new Subject<[string, BlockExtended]>();
|
||||||
txReplaced$ = new Subject<ReplacedTransaction>();
|
txReplaced$ = new Subject<ReplacedTransaction>();
|
||||||
txRbfInfo$ = new Subject<RbfTree>();
|
txRbfInfo$ = new Subject<RbfTree>();
|
||||||
@ -303,6 +307,24 @@ export class StateService {
|
|||||||
map((accMap) => Object.values(accMap).sort((a,b) => b.added - a.added))
|
map((accMap) => Object.values(accMap).sort((a,b) => b.added - a.added))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.stratumJobUpdate$.pipe(
|
||||||
|
scan((acc: Record<string, StratumJob>, update: { state: Record<string, StratumJob> } | { job: StratumJob }) => {
|
||||||
|
if ('state' in update) {
|
||||||
|
// Replace the entire state
|
||||||
|
return update.state;
|
||||||
|
} else {
|
||||||
|
// Update or create a single job entry
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[update.job.pool]: update.job
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {}),
|
||||||
|
shareReplay(1)
|
||||||
|
).subscribe(val => {
|
||||||
|
this.stratumJobs$.next(val);
|
||||||
|
});
|
||||||
|
|
||||||
this.networkChanged$.subscribe((network) => {
|
this.networkChanged$.subscribe((network) => {
|
||||||
this.transactions$ = new BehaviorSubject<TransactionStripped[]>(null);
|
this.transactions$ = new BehaviorSubject<TransactionStripped[]>(null);
|
||||||
this.blocksSubject$.next([]);
|
this.blocksSubject$.next([]);
|
||||||
|
@ -36,6 +36,7 @@ export class WebsocketService {
|
|||||||
private isTrackingAccelerations: boolean = false;
|
private isTrackingAccelerations: boolean = false;
|
||||||
private isTrackingWallet: boolean = false;
|
private isTrackingWallet: boolean = false;
|
||||||
private trackingWalletName: string;
|
private trackingWalletName: string;
|
||||||
|
private isTrackingStratum: string | number | false = false;
|
||||||
private trackingMempoolBlock: number;
|
private trackingMempoolBlock: number;
|
||||||
private trackingMempoolBlockNetwork: string;
|
private trackingMempoolBlockNetwork: string;
|
||||||
private stoppingTrackMempoolBlock: any | null = null;
|
private stoppingTrackMempoolBlock: any | null = null;
|
||||||
@ -143,6 +144,9 @@ export class WebsocketService {
|
|||||||
if (this.isTrackingWallet) {
|
if (this.isTrackingWallet) {
|
||||||
this.startTrackingWallet(this.trackingWalletName);
|
this.startTrackingWallet(this.trackingWalletName);
|
||||||
}
|
}
|
||||||
|
if (this.isTrackingStratum !== false) {
|
||||||
|
this.startTrackStratum(this.isTrackingStratum);
|
||||||
|
}
|
||||||
this.stateService.connectionState$.next(2);
|
this.stateService.connectionState$.next(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +293,18 @@ export class WebsocketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTrackStratum(pool: number | string) {
|
||||||
|
this.websocketSubject.next({ 'track-stratum': pool });
|
||||||
|
this.isTrackingStratum = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTrackStratum() {
|
||||||
|
if (this.isTrackingStratum) {
|
||||||
|
this.websocketSubject.next({ 'track-stratum': null });
|
||||||
|
this.isTrackingStratum = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchStatistics(historicalDate: string) {
|
fetchStatistics(historicalDate: string) {
|
||||||
this.websocketSubject.next({ historicalDate });
|
this.websocketSubject.next({ historicalDate });
|
||||||
}
|
}
|
||||||
@ -512,6 +528,14 @@ export class WebsocketService {
|
|||||||
this.stateService.previousRetarget$.next(response.previousRetarget);
|
this.stateService.previousRetarget$.next(response.previousRetarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.stratumJobs) {
|
||||||
|
this.stateService.stratumJobUpdate$.next({ state: response.stratumJobs });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.stratumJob) {
|
||||||
|
this.stateService.stratumJobUpdate$.next({ job: response.stratumJob });
|
||||||
|
}
|
||||||
|
|
||||||
if (response['tomahawk']) {
|
if (response['tomahawk']) {
|
||||||
this.stateService.serverHealth$.next(response['tomahawk']);
|
this.stateService.serverHealth$.next(response['tomahawk']);
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && replaced">
|
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && replaced">
|
||||||
<button type="button" class="btn btn-sm btn-warning no-cursor {{buttonClass}}" i18n="transaction.replaced|Transaction replaced state">Replaced</button>
|
<button type="button" class="btn btn-sm btn-warning no-cursor {{buttonClass}}" i18n="transaction.replaced|Transaction replaced state">Replaced</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced && removed">
|
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced && (removed || cached)">
|
||||||
<button type="button" class="btn btn-sm btn-warning no-cursor {{buttonClass}}" i18n="transaction.audit.removed|Transaction removed state">Removed</button>
|
<button type="button" class="btn btn-sm btn-warning no-cursor {{buttonClass}}" i18n="transaction.audit.removed|Transaction removed state">Removed</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="!hideUnconfirmed && chainTip != null && !confirmations && !replaced && !removed">
|
<ng-template [ngIf]="!hideUnconfirmed && chainTip != null && !confirmations && !replaced && !(removed || cached)">
|
||||||
<button type="button" class="btn btn-sm btn-danger no-cursor {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
<button type="button" class="btn btn-sm btn-danger no-cursor {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||||
</ng-template>
|
</ng-template>
|
@ -12,6 +12,7 @@ export class ConfirmationsComponent implements OnChanges {
|
|||||||
@Input() height: number;
|
@Input() height: number;
|
||||||
@Input() replaced: boolean = false;
|
@Input() replaced: boolean = false;
|
||||||
@Input() removed: boolean = false;
|
@Input() removed: boolean = false;
|
||||||
|
@Input() cached: boolean = false;
|
||||||
@Input() hideUnconfirmed: boolean = false;
|
@Input() hideUnconfirmed: boolean = false;
|
||||||
@Input() buttonClass: string = '';
|
@Input() buttonClass: string = '';
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ export const MempoolErrors = {
|
|||||||
'unauthorized': `You are not authorized to do this`,
|
'unauthorized': `You are not authorized to do this`,
|
||||||
'faucet_too_soon': `You cannot request any more coins right now. Try again later.`,
|
'faucet_too_soon': `You cannot request any more coins right now. Try again later.`,
|
||||||
'faucet_not_available': `The faucet is not available right now. Try again later.`,
|
'faucet_not_available': `The faucet is not available right now. Try again later.`,
|
||||||
|
'faucet_not_available_no_utxo': `The faucet is not available right now. Please try again once a new block has been mined.`,
|
||||||
'faucet_maximum_reached': `You are not allowed to request more coins`,
|
'faucet_maximum_reached': `You are not allowed to request more coins`,
|
||||||
'faucet_address_not_allowed': `You cannot use this address`,
|
'faucet_address_not_allowed': `You cannot use this address`,
|
||||||
'faucet_below_minimum': `Requested amount is too small`,
|
'faucet_below_minimum': `Requested amount is too small`,
|
||||||
|
@ -4,7 +4,10 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra
|
|||||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||||
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
|
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
|
||||||
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
|
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
|
||||||
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck } from '@fortawesome/free-solid-svg-icons';
|
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft,
|
||||||
|
faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck,
|
||||||
|
faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline,
|
||||||
|
faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes, faCreditCard } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||||
import { MenuComponent } from '@components/menu/menu.component';
|
import { MenuComponent } from '@components/menu/menu.component';
|
||||||
import { PreviewTitleComponent } from '@components/master-page-preview/preview-title.component';
|
import { PreviewTitleComponent } from '@components/master-page-preview/preview-title.component';
|
||||||
@ -80,6 +83,7 @@ import { AmountShortenerPipe } from '@app/shared/pipes/amount-shortener.pipe';
|
|||||||
import { DifficultyAdjustmentsTable } from '@components/difficulty-adjustments-table/difficulty-adjustments-table.components';
|
import { DifficultyAdjustmentsTable } from '@components/difficulty-adjustments-table/difficulty-adjustments-table.components';
|
||||||
import { BlocksList } from '@components/blocks-list/blocks-list.component';
|
import { BlocksList } from '@components/blocks-list/blocks-list.component';
|
||||||
import { RbfList } from '@components/rbf-list/rbf-list.component';
|
import { RbfList } from '@components/rbf-list/rbf-list.component';
|
||||||
|
import { StratumList } from '@components/stratum/stratum-list/stratum-list.component';
|
||||||
import { RewardStatsComponent } from '@components/reward-stats/reward-stats.component';
|
import { RewardStatsComponent } from '@components/reward-stats/reward-stats.component';
|
||||||
import { DataCyDirective } from '@app/data-cy.directive';
|
import { DataCyDirective } from '@app/data-cy.directive';
|
||||||
import { LoadingIndicatorComponent } from '@components/loading-indicator/loading-indicator.component';
|
import { LoadingIndicatorComponent } from '@components/loading-indicator/loading-indicator.component';
|
||||||
@ -121,6 +125,7 @@ import { TwitterLogin } from '@components/twitter-login/twitter-login.component'
|
|||||||
import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-invoice.component';
|
import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-invoice.component';
|
||||||
|
|
||||||
import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/weight-directives/weight-directives';
|
import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/weight-directives/weight-directives';
|
||||||
|
import { GithubLogin } from '../components/github-login.component/github-login.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -198,6 +203,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
DifficultyAdjustmentsTable,
|
DifficultyAdjustmentsTable,
|
||||||
BlocksList,
|
BlocksList,
|
||||||
RbfList,
|
RbfList,
|
||||||
|
StratumList,
|
||||||
DataCyDirective,
|
DataCyDirective,
|
||||||
RewardStatsComponent,
|
RewardStatsComponent,
|
||||||
LoadingIndicatorComponent,
|
LoadingIndicatorComponent,
|
||||||
@ -237,6 +243,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
TwitterWidgetComponent,
|
TwitterWidgetComponent,
|
||||||
FaucetComponent,
|
FaucetComponent,
|
||||||
TwitterLogin,
|
TwitterLogin,
|
||||||
|
GithubLogin,
|
||||||
BitcoinInvoiceComponent,
|
BitcoinInvoiceComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
@ -342,6 +349,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
AmountShortenerPipe,
|
AmountShortenerPipe,
|
||||||
DifficultyAdjustmentsTable,
|
DifficultyAdjustmentsTable,
|
||||||
BlocksList,
|
BlocksList,
|
||||||
|
StratumList,
|
||||||
DataCyDirective,
|
DataCyDirective,
|
||||||
RewardStatsComponent,
|
RewardStatsComponent,
|
||||||
LoadingIndicatorComponent,
|
LoadingIndicatorComponent,
|
||||||
@ -370,6 +378,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
HttpErrorComponent,
|
HttpErrorComponent,
|
||||||
TwitterWidgetComponent,
|
TwitterWidgetComponent,
|
||||||
TwitterLogin,
|
TwitterLogin,
|
||||||
|
GithubLogin,
|
||||||
BitcoinInvoiceComponent,
|
BitcoinInvoiceComponent,
|
||||||
BitcoinsatoshisPipe,
|
BitcoinsatoshisPipe,
|
||||||
|
|
||||||
@ -451,5 +460,9 @@ export class SharedModule {
|
|||||||
library.addIcons(faTimeline);
|
library.addIcons(faTimeline);
|
||||||
library.addIcons(faCircleXmark);
|
library.addIcons(faCircleXmark);
|
||||||
library.addIcons(faCalendarCheck);
|
library.addIcons(faCalendarCheck);
|
||||||
|
library.addIcons(faMoneyBillTrendUp);
|
||||||
|
library.addIcons(faRobot);
|
||||||
|
library.addIcons(faShareNodes);
|
||||||
|
library.addIcons(faCreditCard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,8 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 127.0.0.1:80;
|
listen 80;
|
||||||
|
listen [::]:80
|
||||||
include /etc/nginx/nginx-mempool.conf;
|
include /etc/nginx/nginx-mempool.conf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ whitelist=127.0.0.1
|
|||||||
whitelist=103.99.168.0/22
|
whitelist=103.99.168.0/22
|
||||||
whitelist=2401:b140::/32
|
whitelist=2401:b140::/32
|
||||||
blocksxor=0
|
blocksxor=0
|
||||||
|
logtimemicros=1
|
||||||
#uacomment=@wiz
|
#uacomment=@wiz
|
||||||
|
|
||||||
[main]
|
[main]
|
||||||
|
@ -26,12 +26,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/elements/socket/esplora-elements-liquid",
|
"UNIX_SOCKET_PATH": "/elements/socket/esplora-elements-liquid",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3001",
|
"http://node201.hnl.mempool.space:3001",
|
||||||
"http://node202.fmt.mempool.space:3001",
|
"http://node202.hnl.mempool.space:3001",
|
||||||
"http://node203.fmt.mempool.space:3001",
|
"http://node203.hnl.mempool.space:3001",
|
||||||
"http://node204.fmt.mempool.space:3001",
|
"http://node204.hnl.mempool.space:3001",
|
||||||
"http://node205.fmt.mempool.space:3001",
|
"http://node201.sg1.mempool.space:3001",
|
||||||
"http://node206.fmt.mempool.space:3001",
|
"http://node202.sg1.mempool.space:3001",
|
||||||
|
"http://node203.sg1.mempool.space:3001",
|
||||||
|
"http://node204.sg1.mempool.space:3001",
|
||||||
"http://node201.va1.mempool.space:3001",
|
"http://node201.va1.mempool.space:3001",
|
||||||
"http://node202.va1.mempool.space:3001",
|
"http://node202.va1.mempool.space:3001",
|
||||||
"http://node203.va1.mempool.space:3001",
|
"http://node203.va1.mempool.space:3001",
|
||||||
|
@ -26,12 +26,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/elements/socket/esplora-elements-liquidtestnet",
|
"UNIX_SOCKET_PATH": "/elements/socket/esplora-elements-liquidtestnet",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3004",
|
"http://node201.hnl.mempool.space:3004",
|
||||||
"http://node202.fmt.mempool.space:3004",
|
"http://node202.hnl.mempool.space:3004",
|
||||||
"http://node203.fmt.mempool.space:3004",
|
"http://node203.hnl.mempool.space:3004",
|
||||||
"http://node204.fmt.mempool.space:3004",
|
"http://node204.hnl.mempool.space:3004",
|
||||||
"http://node205.fmt.mempool.space:3004",
|
"http://node201.sg1.mempool.space:3004",
|
||||||
"http://node206.fmt.mempool.space:3004",
|
"http://node202.sg1.mempool.space:3004",
|
||||||
|
"http://node203.sg1.mempool.space:3004",
|
||||||
|
"http://node204.sg1.mempool.space:3004",
|
||||||
"http://node201.va1.mempool.space:3004",
|
"http://node201.va1.mempool.space:3004",
|
||||||
"http://node202.va1.mempool.space:3004",
|
"http://node202.va1.mempool.space:3004",
|
||||||
"http://node203.va1.mempool.space:3004",
|
"http://node203.va1.mempool.space:3004",
|
||||||
|
@ -19,12 +19,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet",
|
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3000",
|
"http://node201.hnl.mempool.space:3000",
|
||||||
"http://node202.fmt.mempool.space:3000",
|
"http://node202.hnl.mempool.space:3000",
|
||||||
"http://node203.fmt.mempool.space:3000",
|
"http://node203.hnl.mempool.space:3000",
|
||||||
"http://node204.fmt.mempool.space:3000",
|
"http://node204.hnl.mempool.space:3000",
|
||||||
"http://node205.fmt.mempool.space:3000",
|
"http://node201.sg1.mempool.space:3000",
|
||||||
"http://node206.fmt.mempool.space:3000",
|
"http://node202.sg1.mempool.space:3000",
|
||||||
|
"http://node203.sg1.mempool.space:3000",
|
||||||
|
"http://node204.sg1.mempool.space:3000",
|
||||||
"http://node201.va1.mempool.space:3000",
|
"http://node201.va1.mempool.space:3000",
|
||||||
"http://node202.va1.mempool.space:3000",
|
"http://node202.va1.mempool.space:3000",
|
||||||
"http://node203.va1.mempool.space:3000",
|
"http://node203.va1.mempool.space:3000",
|
||||||
|
@ -30,7 +30,8 @@
|
|||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"PORT": 8332,
|
"PORT": 8332,
|
||||||
"USERNAME": "__BITCOIN_RPC_USER__",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
"PASSWORD": "__BITCOIN_RPC_PASS__",
|
||||||
|
"DEBUG_LOG_PATH": "/bitcoin/debug.log"
|
||||||
},
|
},
|
||||||
"SECOND_CORE_RPC": {
|
"SECOND_CORE_RPC": {
|
||||||
"PORT": 8302,
|
"PORT": 8302,
|
||||||
@ -40,12 +41,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet",
|
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3000",
|
"http://node201.hnl.mempool.space:3000",
|
||||||
"http://node202.fmt.mempool.space:3000",
|
"http://node202.hnl.mempool.space:3000",
|
||||||
"http://node203.fmt.mempool.space:3000",
|
"http://node203.hnl.mempool.space:3000",
|
||||||
"http://node204.fmt.mempool.space:3000",
|
"http://node204.hnl.mempool.space:3000",
|
||||||
"http://node205.fmt.mempool.space:3000",
|
"http://node201.sg1.mempool.space:3000",
|
||||||
"http://node206.fmt.mempool.space:3000",
|
"http://node202.sg1.mempool.space:3000",
|
||||||
|
"http://node203.sg1.mempool.space:3000",
|
||||||
|
"http://node204.sg1.mempool.space:3000",
|
||||||
"http://node201.va1.mempool.space:3000",
|
"http://node201.va1.mempool.space:3000",
|
||||||
"http://node202.va1.mempool.space:3000",
|
"http://node202.va1.mempool.space:3000",
|
||||||
"http://node203.va1.mempool.space:3000",
|
"http://node203.va1.mempool.space:3000",
|
||||||
@ -101,12 +104,14 @@
|
|||||||
"STATISTICS": true,
|
"STATISTICS": true,
|
||||||
"STATISTICS_START_TIME": "24h",
|
"STATISTICS_START_TIME": "24h",
|
||||||
"SERVERS": [
|
"SERVERS": [
|
||||||
"node201.fmt.mempool.space",
|
"node201.hnl.mempool.space",
|
||||||
"node202.fmt.mempool.space",
|
"node202.hnl.mempool.space",
|
||||||
"node203.fmt.mempool.space",
|
"node203.hnl.mempool.space",
|
||||||
"node204.fmt.mempool.space",
|
"node204.hnl.mempool.space",
|
||||||
"node205.fmt.mempool.space",
|
"node201.sg1.mempool.space",
|
||||||
"node206.fmt.mempool.space",
|
"node202.sg1.mempool.space",
|
||||||
|
"node203.sg1.mempool.space",
|
||||||
|
"node204.sg1.mempool.space",
|
||||||
"node201.va1.mempool.space",
|
"node201.va1.mempool.space",
|
||||||
"node202.va1.mempool.space",
|
"node202.va1.mempool.space",
|
||||||
"node203.va1.mempool.space",
|
"node203.va1.mempool.space",
|
||||||
@ -154,5 +159,9 @@
|
|||||||
"WALLETS": {
|
"WALLETS": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"WALLETS": ["BITB", "3350"]
|
"WALLETS": ["BITB", "3350"]
|
||||||
|
},
|
||||||
|
"STRATUM": {
|
||||||
|
"ENABLED": true,
|
||||||
|
"API": "http://127.0.0.1:81/api/v1/stratum/ws"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet",
|
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3003",
|
"http://node201.hnl.mempool.space:3003",
|
||||||
"http://node202.fmt.mempool.space:3003",
|
"http://node202.hnl.mempool.space:3003",
|
||||||
"http://node203.fmt.mempool.space:3003",
|
"http://node203.hnl.mempool.space:3003",
|
||||||
"http://node204.fmt.mempool.space:3003",
|
"http://node204.hnl.mempool.space:3003",
|
||||||
"http://node205.fmt.mempool.space:3003",
|
"http://node201.sg1.mempool.space:3003",
|
||||||
"http://node206.fmt.mempool.space:3003",
|
"http://node202.sg1.mempool.space:3003",
|
||||||
|
"http://node203.sg1.mempool.space:3003",
|
||||||
|
"http://node204.sg1.mempool.space:3003",
|
||||||
"http://node201.va1.mempool.space:3003",
|
"http://node201.va1.mempool.space:3003",
|
||||||
"http://node202.va1.mempool.space:3003",
|
"http://node202.va1.mempool.space:3003",
|
||||||
"http://node203.va1.mempool.space:3003",
|
"http://node203.va1.mempool.space:3003",
|
||||||
|
@ -28,12 +28,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet",
|
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3003",
|
"http://node201.hnl.mempool.space:3003",
|
||||||
"http://node202.fmt.mempool.space:3003",
|
"http://node202.hnl.mempool.space:3003",
|
||||||
"http://node203.fmt.mempool.space:3003",
|
"http://node203.hnl.mempool.space:3003",
|
||||||
"http://node204.fmt.mempool.space:3003",
|
"http://node204.hnl.mempool.space:3003",
|
||||||
"http://node205.fmt.mempool.space:3003",
|
"http://node201.sg1.mempool.space:3003",
|
||||||
"http://node206.fmt.mempool.space:3003",
|
"http://node202.sg1.mempool.space:3003",
|
||||||
|
"http://node203.sg1.mempool.space:3003",
|
||||||
|
"http://node204.sg1.mempool.space:3003",
|
||||||
"http://node201.va1.mempool.space:3003",
|
"http://node201.va1.mempool.space:3003",
|
||||||
"http://node202.va1.mempool.space:3003",
|
"http://node202.va1.mempool.space:3003",
|
||||||
"http://node203.va1.mempool.space:3003",
|
"http://node203.va1.mempool.space:3003",
|
||||||
|
@ -19,12 +19,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet",
|
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3002",
|
"http://node201.hnl.mempool.space:3002",
|
||||||
"http://node202.fmt.mempool.space:3002",
|
"http://node202.hnl.mempool.space:3002",
|
||||||
"http://node203.fmt.mempool.space:3002",
|
"http://node203.hnl.mempool.space:3002",
|
||||||
"http://node204.fmt.mempool.space:3002",
|
"http://node204.hnl.mempool.space:3002",
|
||||||
"http://node205.fmt.mempool.space:3002",
|
"http://node201.sg1.mempool.space:3002",
|
||||||
"http://node206.fmt.mempool.space:3002",
|
"http://node202.sg1.mempool.space:3002",
|
||||||
|
"http://node203.sg1.mempool.space:3002",
|
||||||
|
"http://node204.sg1.mempool.space:3002",
|
||||||
"http://node201.va1.mempool.space:3002",
|
"http://node201.va1.mempool.space:3002",
|
||||||
"http://node202.va1.mempool.space:3002",
|
"http://node202.va1.mempool.space:3002",
|
||||||
"http://node203.va1.mempool.space:3002",
|
"http://node203.va1.mempool.space:3002",
|
||||||
|
@ -28,12 +28,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet",
|
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3002",
|
"http://node201.hnl.mempool.space:3002",
|
||||||
"http://node202.fmt.mempool.space:3002",
|
"http://node202.hnl.mempool.space:3002",
|
||||||
"http://node203.fmt.mempool.space:3002",
|
"http://node203.hnl.mempool.space:3002",
|
||||||
"http://node204.fmt.mempool.space:3002",
|
"http://node204.hnl.mempool.space:3002",
|
||||||
"http://node205.fmt.mempool.space:3002",
|
"http://node201.sg1.mempool.space:3002",
|
||||||
"http://node206.fmt.mempool.space:3002",
|
"http://node202.sg1.mempool.space:3002",
|
||||||
|
"http://node203.sg1.mempool.space:3002",
|
||||||
|
"http://node204.sg1.mempool.space:3002",
|
||||||
"http://node201.va1.mempool.space:3002",
|
"http://node201.va1.mempool.space:3002",
|
||||||
"http://node202.va1.mempool.space:3002",
|
"http://node202.va1.mempool.space:3002",
|
||||||
"http://node203.va1.mempool.space:3002",
|
"http://node203.va1.mempool.space:3002",
|
||||||
|
@ -28,12 +28,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet4",
|
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet4",
|
||||||
"FALLBACK": [
|
"FALLBACK": [
|
||||||
"http://node201.fmt.mempool.space:3005",
|
"http://node201.hnl.mempool.space:3005",
|
||||||
"http://node202.fmt.mempool.space:3005",
|
"http://node202.hnl.mempool.space:3005",
|
||||||
"http://node203.fmt.mempool.space:3005",
|
"http://node203.hnl.mempool.space:3005",
|
||||||
"http://node204.fmt.mempool.space:3005",
|
"http://node204.hnl.mempool.space:3005",
|
||||||
"http://node205.fmt.mempool.space:3005",
|
"http://node201.sg1.mempool.space:3005",
|
||||||
"http://node206.fmt.mempool.space:3005",
|
"http://node202.sg1.mempool.space:3005",
|
||||||
|
"http://node203.sg1.mempool.space:3005",
|
||||||
|
"http://node204.sg1.mempool.space:3005",
|
||||||
"http://node201.va1.mempool.space:3005",
|
"http://node201.va1.mempool.space:3005",
|
||||||
"http://node202.va1.mempool.space:3005",
|
"http://node202.va1.mempool.space:3005",
|
||||||
"http://node203.va1.mempool.space:3005",
|
"http://node203.va1.mempool.space:3005",
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
"TESTNET4_ENABLED": true,
|
"TESTNET4_ENABLED": true,
|
||||||
"LIQUID_ENABLED": false,
|
"LIQUID_ENABLED": false,
|
||||||
"LIQUID_TESTNET_ENABLED": false,
|
"LIQUID_TESTNET_ENABLED": false,
|
||||||
"BISQ_ENABLED": true,
|
"STRATUM_ENABLED": true,
|
||||||
"BISQ_SEPARATE_BACKEND": true,
|
|
||||||
"SIGNET_ENABLED": true,
|
"SIGNET_ENABLED": true,
|
||||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
proxy_cache_path /var/cache/nginx/services keys_zone=services:200m levels=1:2 inactive=30d max_size=200m;
|
proxy_cache_path /var/cache/nginx/services keys_zone=services:200m levels=1:2 inactive=30d max_size=200m;
|
||||||
proxy_cache_path /var/cache/nginx/apihot keys_zone=apihot:200m levels=1:2 inactive=60m max_size=20m;
|
proxy_cache_path /var/cache/nginx/apihot keys_zone=apihot:200m levels=1:2 inactive=60m max_size=20m;
|
||||||
proxy_cache_path /var/cache/nginx/apiwarm keys_zone=apiwarm:200m levels=1:2 inactive=24h max_size=200m;
|
proxy_cache_path /var/cache/nginx/apiwarm keys_zone=apiwarm:200m levels=1:2 inactive=24h max_size=200m;
|
||||||
proxy_cache_path /var/cache/nginx/apinormal keys_zone=apinormal:200m levels=1:2 inactive=30d max_size=2000m;
|
proxy_cache_path /var/cache/nginx/apinormal keys_zone=apinormal:500m levels=1:2 inactive=24h max_size=2000m;
|
||||||
proxy_cache_path /var/cache/nginx/apicold keys_zone=apicold:200m levels=1:2 inactive=60d max_size=2000m;
|
proxy_cache_path /var/cache/nginx/apicold keys_zone=apicold:200m levels=1:2 inactive=60d max_size=2000m;
|
||||||
|
|
||||||
proxy_cache_path /var/cache/nginx/unfurler keys_zone=unfurler:200m levels=1:2 inactive=30d max_size=2000m;
|
proxy_cache_path /var/cache/nginx/unfurler keys_zone=unfurler:200m levels=1:2 inactive=30d max_size=2000m;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user