Compare commits
33 Commits
knorrium/u
...
mononaut/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c468a446c2 | ||
|
|
a79c165b30 | ||
|
|
7f07c5cbab | ||
|
|
c021a15d0b | ||
|
|
a2009aa322 | ||
|
|
e6965dac80 | ||
|
|
c4a7a2e781 | ||
|
|
03bea14f89 | ||
|
|
834f9a9f6d | ||
|
|
518297494f | ||
|
|
15e67bc77f | ||
|
|
ff383f9c58 | ||
|
|
ed44e27991 | ||
|
|
a869ea5ec4 | ||
|
|
fc4a0f7461 | ||
|
|
b3f21a10b9 | ||
|
|
4290e00376 | ||
|
|
3b91a1437a | ||
|
|
363fa3d877 | ||
|
|
2e44ea3f01 | ||
|
|
cac62765a1 | ||
|
|
23713a11c2 | ||
|
|
469faf7456 | ||
|
|
665a12a040 | ||
|
|
b42431f14a | ||
|
|
69dfcbe6fa | ||
|
|
b454fa09d2 | ||
|
|
019101862f | ||
|
|
817076fcbd | ||
|
|
778837322d | ||
|
|
58e6a78579 | ||
|
|
e77dd114f4 | ||
|
|
ff235760b2 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
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/')"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ["20", "22"]
|
node: ["20", "21"]
|
||||||
flavor: ["dev", "prod"]
|
flavor: ["dev", "prod"]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
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/')"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ["20", "22"]
|
node: ["20", "21"]
|
||||||
flavor: ["dev", "prod"]
|
flavor: ["dev", "prod"]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
@@ -263,7 +263,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 20
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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",
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
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",
|
||||||
|
|||||||
@@ -397,13 +397,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) {
|
||||||
@@ -421,6 +421,13 @@
|
|||||||
<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')">
|
||||||
|
<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>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -443,7 +450,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">
|
||||||
@@ -459,7 +466,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">
|
||||||
@@ -484,8 +491,13 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -65,6 +65,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
@Input() cashappEnabled: boolean = true;
|
@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;
|
||||||
@@ -117,6 +118,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;
|
||||||
@@ -234,6 +236,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(() => {
|
||||||
@@ -454,6 +460,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: () => {
|
||||||
@@ -710,6 +718,109 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
*/
|
*/
|
||||||
@@ -955,6 +1066,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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -21,10 +21,8 @@
|
|||||||
<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>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="220px" redirectTo="/testnet4/faucet" buttonString="Sign up with Github"></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 Github"></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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 : ''">
|
||||||
|
<svg height="32" viewBox="0 0 18 16" width="32" style="fill: white;">
|
||||||
|
<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>
|
||||||
|
<span class="ml-2 text-light align-middle">{{ buttonString }}</span>
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -267,7 +267,11 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@for (branch of job.merkleBranches; track $index) {
|
@for (branch of job.merkleBranches; track $index) {
|
||||||
<td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''"></td>
|
@if ($index === 0 && branch) {
|
||||||
|
<a [routerLink]="['/tx' | relativeUrl, reverseHash(branch)]"><td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''"><fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 14px; color: white"></fa-icon></td></a>
|
||||||
|
} @else {
|
||||||
|
<td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''"></td>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@for (_ of [].constructor(Math.max(0, 12 - job.merkleBranches.length)); track $index) {
|
@for (_ of [].constructor(Math.max(0, 12 - job.merkleBranches.length)); track $index) {
|
||||||
<td class="merkle empty-branch"></td>
|
<td class="merkle empty-branch"></td>
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ div.scrollable {
|
|||||||
|
|
||||||
.merkle {
|
.merkle {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-branch {
|
.empty-branch {
|
||||||
|
|||||||
@@ -361,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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,30 +7,27 @@
|
|||||||
<table *ngIf="poolsReady && (rows$ | async) as rows;" class="stratum-table">
|
<table *ngIf="poolsReady && (rows$ | async) as rows;" class="stratum-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="height">Height</td>
|
|
||||||
<td class="reward">Reward</td>
|
|
||||||
<td class="tag">Coinbase Tag</td>
|
|
||||||
<td class="merkle" [attr.colspan]="rows[0]?.merkleCells?.length || 4">
|
<td class="merkle" [attr.colspan]="rows[0]?.merkleCells?.length || 4">
|
||||||
Merkle Branches
|
Merkle Branches
|
||||||
</td>
|
</td>
|
||||||
<td class="pool">Pool</td>
|
<td class="pool">Pool</td>
|
||||||
|
<td class="tag">Coinbase Tag</td>
|
||||||
|
<td class="reward">Reward</td>
|
||||||
|
<td class="height">Height</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (row of rows; track row.job.pool) {
|
@for (row of rows; track row.job.pool) {
|
||||||
<tr>
|
<tr>
|
||||||
<td class="height">
|
|
||||||
{{ row.job.height }}
|
|
||||||
</td>
|
|
||||||
<td class="reward">
|
|
||||||
<app-amount [satoshis]="row.job.reward"></app-amount>
|
|
||||||
</td>
|
|
||||||
<td class="tag">
|
|
||||||
{{ row.job.tag }}
|
|
||||||
</td>
|
|
||||||
@for (cell of row.merkleCells; track $index) {
|
@for (cell of row.merkleCells; track $index) {
|
||||||
<td class="merkle" [style.background-color]="cell.hash ? '#' + cell.hash.slice(0, 6) : ''">
|
<td class="merkle" [style.background-color]="cell.hash ? '#' + cell.hash.slice(0, 6) : ''">
|
||||||
<div class="pipe-segment" [class]="pipeToClass(cell.type)"></div>
|
@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>
|
||||||
}
|
}
|
||||||
<td class="pool">
|
<td class="pool">
|
||||||
@@ -41,6 +38,15 @@
|
|||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -92,6 +92,36 @@ td {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.badge {
|
||||||
|
|||||||
@@ -48,14 +48,16 @@ function parseTag(scriptSig: string): string {
|
|||||||
return (ascii.match(/\/.*\//)?.[0] || ascii).trim();
|
return (ascii.match(/\/.*\//)?.[0] || ascii).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMerkleBranchIds(merkleBranches: string[], numBranches: number): string[] {
|
function getMerkleBranchIds(merkleBranches: string[], numBranches: number, poolId: number): string[] {
|
||||||
let lastHash = '';
|
let lastHash = '';
|
||||||
const ids: string[] = [];
|
const ids: string[] = [];
|
||||||
for (let i = 0; i < numBranches; i++) {
|
for (let i = 0; i < numBranches; i++) {
|
||||||
if (merkleBranches[i]) {
|
if (merkleBranches[i]) {
|
||||||
lastHash = merkleBranches[i];
|
lastHash = merkleBranches[i];
|
||||||
|
ids.push(`${i}-${lastHash}`);
|
||||||
|
} else {
|
||||||
|
ids.push(`${i}-${lastHash}-${poolId}`);
|
||||||
}
|
}
|
||||||
ids.push(`${i}-${lastHash}`);
|
|
||||||
}
|
}
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
@@ -98,7 +100,7 @@ export class StratumList implements OnInit, OnDestroy {
|
|||||||
const numBranches = Math.max(...Object.values(rawJobs).map(job => job.merkleBranches.length));
|
const numBranches = Math.max(...Object.values(rawJobs).map(job => job.merkleBranches.length));
|
||||||
const jobs: Record<string, TaggedStratumJob> = {};
|
const jobs: Record<string, TaggedStratumJob> = {};
|
||||||
for (const [id, job] of Object.entries(rawJobs)) {
|
for (const [id, job] of Object.entries(rawJobs)) {
|
||||||
jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches) };
|
jobs[id] = { ...job, tag: parseTag(job.scriptsig), merkleBranchIds: getMerkleBranchIds(job.merkleBranches, numBranches, job.pool) };
|
||||||
}
|
}
|
||||||
if (Object.keys(jobs).length === 0) {
|
if (Object.keys(jobs).length === 0) {
|
||||||
return [];
|
return [];
|
||||||
@@ -196,6 +198,10 @@ export class StratumList implements OnInit, OnDestroy {
|
|||||||
}[type];
|
}[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reverseHash(hash: string) {
|
||||||
|
return hash.match(/../g).reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.websocketService.stopTrackStratum();
|
this.websocketService.stopTrackStratum();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ export class ServicesApiServices {
|
|||||||
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 });
|
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[]> {
|
||||||
return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations`);
|
return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa
|
|||||||
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft,
|
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft,
|
||||||
faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck,
|
faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck,
|
||||||
faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline,
|
faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline,
|
||||||
faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes } from '@fortawesome/free-solid-svg-icons';
|
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';
|
||||||
@@ -125,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: [
|
||||||
@@ -242,6 +243,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
TwitterWidgetComponent,
|
TwitterWidgetComponent,
|
||||||
FaucetComponent,
|
FaucetComponent,
|
||||||
TwitterLogin,
|
TwitterLogin,
|
||||||
|
GithubLogin,
|
||||||
BitcoinInvoiceComponent,
|
BitcoinInvoiceComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
@@ -376,6 +378,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
HttpErrorComponent,
|
HttpErrorComponent,
|
||||||
TwitterWidgetComponent,
|
TwitterWidgetComponent,
|
||||||
TwitterLogin,
|
TwitterLogin,
|
||||||
|
GithubLogin,
|
||||||
BitcoinInvoiceComponent,
|
BitcoinInvoiceComponent,
|
||||||
BitcoinsatoshisPipe,
|
BitcoinsatoshisPipe,
|
||||||
|
|
||||||
@@ -460,5 +463,6 @@ export class SharedModule {
|
|||||||
library.addIcons(faMoneyBillTrendUp);
|
library.addIcons(faMoneyBillTrendUp);
|
||||||
library.addIcons(faRobot);
|
library.addIcons(faRobot);
|
||||||
library.addIcons(faShareNodes);
|
library.addIcons(faShareNodes);
|
||||||
|
library.addIcons(faCreditCard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,21 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<!-- LOAD BEARING COMMENTS - DO NOT TOUCH! -->
|
||||||
|
<!-- TITLE -->
|
||||||
<title>mempool - Bitcoin Explorer</title>
|
<title>mempool - Bitcoin Explorer</title>
|
||||||
|
<!-- END TITLE -->
|
||||||
|
|
||||||
|
<!-- CONFIG -->
|
||||||
<script src="/resources/config.js"></script>
|
<script src="/resources/config.js"></script>
|
||||||
|
<!-- END CONFIG -->
|
||||||
|
<!-- CUSTOMIZATION -->
|
||||||
<script src="/resources/customize.js"></script>
|
<script src="/resources/customize.js"></script>
|
||||||
|
<!-- END CUSTOMIZATION -->
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
|
<!-- META -->
|
||||||
<meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more." />
|
<meta name="description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more." />
|
||||||
<meta property="og:image" content="https://mempool.space/resources/previews/mempool-space-preview.jpg" />
|
<meta property="og:image" content="https://mempool.space/resources/previews/mempool-space-preview.jpg" />
|
||||||
<meta property="og:image:type" content="image/jpeg" />
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
@@ -21,19 +31,21 @@
|
|||||||
<meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more." />
|
<meta name="twitter:description" content="Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more." />
|
||||||
<meta name="twitter:image" content="https://mempool.space/resources/previews/mempool-space-preview.jpg" />
|
<meta name="twitter:image" content="https://mempool.space/resources/previews/mempool-space-preview.jpg" />
|
||||||
<meta name="twitter:domain" content="mempool.space">
|
<meta name="twitter:domain" content="mempool.space">
|
||||||
|
<!-- END META -->
|
||||||
|
|
||||||
|
<!-- FAVICONS -->
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/resources/favicons/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/resources/favicons/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/resources/favicons/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="/resources/favicons/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/resources/favicons/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="/resources/favicons/favicon-16x16.png">
|
||||||
<link rel="manifest" href="/resources/favicons/site.webmanifest">
|
<link rel="manifest" href="/resources/favicons/site.webmanifest">
|
||||||
<link rel="shortcut icon" href="/resources/favicons/favicon.ico">
|
<link rel="shortcut icon" href="/resources/favicons/favicon.ico">
|
||||||
<link id="canonical" rel="canonical" href="https://mempool.space">
|
<link id="canonical" rel="canonical" href="https://mempool.space">
|
||||||
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="msapplication-TileColor" content="#000000">
|
<meta name="msapplication-TileColor" content="#000000">
|
||||||
<meta name="msapplication-config" content="/resources/favicons/browserconfig.xml">
|
<meta name="msapplication-config" content="/resources/favicons/browserconfig.xml">
|
||||||
<meta name="theme-color" content="#1d1f31">
|
<meta name="theme-color" content="#1d1f31">
|
||||||
|
<!-- END FAVICONS -->
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
128
production/mempool-build-custom.js
Normal file
128
production/mempool-build-custom.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
"domains": ["mempool.space"],
|
||||||
|
"theme": "default",
|
||||||
|
"enterprise": "mempool",
|
||||||
|
"branding": {
|
||||||
|
"name": "mempool",
|
||||||
|
"title": "mempool",
|
||||||
|
"site_id": 5,
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"title": "mempool - Bitcoin Explorer",
|
||||||
|
"description": "Explore the full Bitcoin ecosystem with The Mempool Open Source Project®. See the real-time status of your transactions, get network info, and more.",
|
||||||
|
},
|
||||||
|
"unfurls": {
|
||||||
|
"preview": {
|
||||||
|
"src": "https://mempool.space/resources/previews/mempool-space-preview.jpg",
|
||||||
|
"type": "image/jpeg",
|
||||||
|
"width": "2000",
|
||||||
|
"height": "1000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepMerge(target, source) {
|
||||||
|
for (const key in source) {
|
||||||
|
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
||||||
|
target[key] = target[key] || {};
|
||||||
|
deepMerge(target[key], source[key]);
|
||||||
|
} else {
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDefaultsToConfig(config) {
|
||||||
|
return deepMerge(structuredClone(defaultConfig), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function substitute(indexhtml, config) {
|
||||||
|
let newhtml = indexhtml;
|
||||||
|
// substitute title
|
||||||
|
newhtml = newhtml.replace(/\<\!\-\- TITLE \-\-\>.*\<\!\-\- END TITLE \-\-\>/gis, `<title>${config.meta.title}</title>`);
|
||||||
|
|
||||||
|
// substitute customization script
|
||||||
|
newhtml = newhtml.replace(/\<\!\-\- CUSTOMIZATION \-\-\>.*\<\!\-\- END CUSTOMIZATION \-\-\>/gis, `<script>
|
||||||
|
window.__env = window.__env || {};
|
||||||
|
window.__env.CUSTOMIZATION = 'auto';
|
||||||
|
window.__env.customize = ${JSON.stringify(config)};
|
||||||
|
</script>`);
|
||||||
|
|
||||||
|
// substitute meta tags
|
||||||
|
newhtml = newhtml.replace(/\<\!\-\- META \-\-\>.*\<\!\-\- END META \-\-\>/gis, `<meta name="description" content="${config.meta.description}" />
|
||||||
|
<meta property="og:image" content="${config.unfurls.preview.src}" />
|
||||||
|
<meta property="og:image:type" content="${config.unfurls.preview.type}" />
|
||||||
|
<meta property="og:image:width" content="${config.unfurls.preview.width}" />
|
||||||
|
<meta property="og:image:height" content="${config.unfurls.preview.height}" />
|
||||||
|
<meta property="og:description" content="${config.meta.description}" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:site" content="@mempool">
|
||||||
|
<meta name="twitter:creator" content="@mempool">
|
||||||
|
<meta name="twitter:title" content="${config.meta.title}">
|
||||||
|
<meta name="twitter:description" content="${config.meta.description}" />
|
||||||
|
<meta name="twitter:image" content="${config.unfurls.preview.src}" />
|
||||||
|
<meta name="twitter:domain" content="${config.domains[0]}">`);
|
||||||
|
|
||||||
|
|
||||||
|
// substitute favicons
|
||||||
|
newhtml = newhtml.replace(/\<\!\-\- FAVICONS -->.*\<\!\-\- END FAVICONS \-\-\>/gis, `<link rel="apple-touch-icon" sizes="180x180" href="/resources/${config.enterprise}/favicons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/resources/${config.enterprise}/favicons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/resources/${config.enterprise}/favicons/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/resources/${config.enterprise}/favicons/site.webmanifest">
|
||||||
|
<link rel="shortcut icon" href="/resources/${config.enterprise}/favicons/favicon.ico">
|
||||||
|
<link id="canonical" rel="canonical" href="${config.domains[0]}">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="msapplication-TileColor" content="#000000">
|
||||||
|
<meta name="msapplication-config" content="/resources/${config.enterprise}/favicons/browserconfig.xml">
|
||||||
|
<meta name="theme-color" content="#1d1f31">`);
|
||||||
|
|
||||||
|
return newhtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const servicesHost = process.argv[2] || 'http://localhost:9000';
|
||||||
|
const mempoolDir = process.argv[3] || '../';
|
||||||
|
|
||||||
|
console.log('fetching list of custom builds');
|
||||||
|
const customBuilds = await (await fetch(`${servicesHost}/api/v1/services/custom/list`)).json();
|
||||||
|
|
||||||
|
// fetch config for each custom build from `$SERVICES/api/v1/services/custom/config/<custom_build_id>`
|
||||||
|
const customConfigs = await Promise.all(customBuilds.map(async (build) => {
|
||||||
|
console.log(`fetching config for ${build} `);
|
||||||
|
return addDefaultsToConfig(await (await fetch(`${servicesHost}/api/v1/services/custom/config/${build}`)).json());
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// for each custom build config:
|
||||||
|
let i = 0;
|
||||||
|
for (const config of customConfigs) {
|
||||||
|
console.log(`generating ${config.enterprise} build (${i + 1}/${customConfigs.length})`);
|
||||||
|
const browserDir = `${mempoolDir}/frontend/dist/mempool/browser`;
|
||||||
|
const locales = fs.readdirSync(browserDir)
|
||||||
|
.filter(file => fs.statSync(`${browserDir}/${file}`).isDirectory())
|
||||||
|
.filter(file => fs.existsSync(`${browserDir}/${file}/index.html`));
|
||||||
|
|
||||||
|
// Process each locale's index.html
|
||||||
|
for (const locale of locales) {
|
||||||
|
const indexPath = `${browserDir}/${locale}/index.html`;
|
||||||
|
const indexContent = fs.readFileSync(indexPath, 'utf-8').toString();
|
||||||
|
const processedHtml = substitute(indexContent, config);
|
||||||
|
|
||||||
|
// Save processed HTML
|
||||||
|
const outputPath = `${browserDir}/${locale}/index.${config.enterprise}.html`;
|
||||||
|
fs.writeFileSync(outputPath, processedHtml);
|
||||||
|
console.log(`updated index.${config.enterprise}.html for locale ${locale}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`finished generating ${config.enterprise} build`);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('finished updating custom builds');
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
Reference in New Issue
Block a user