mirror of
https://github.com/mempool/mempool.git
synced 2025-03-13 11:36:07 +01:00
Merge branch 'master' into taproot-channels
This commit is contained in:
commit
562cd5683a
111 changed files with 2487 additions and 1412 deletions
|
@ -23,6 +23,7 @@ Mempool can be conveniently installed on the following full-node distros:
|
|||
- [RoninDojo](https://code.samourai.io/ronindojo/RoninDojo)
|
||||
- [myNode](https://github.com/mynodebtc/mynode)
|
||||
- [Start9](https://github.com/Start9Labs/embassy-os)
|
||||
- [nix-bitcoin](https://github.com/fort-nix/nix-bitcoin/blob/a1eacce6768ca4894f365af8f79be5bbd594e1c3/examples/configuration.nix#L129)
|
||||
|
||||
**We highly recommend you deploy your own Mempool instance this way.** No matter which option you pick, you'll be able to get your own fully-sovereign instance of Mempool up quickly without needing to fiddle with any settings.
|
||||
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
"PORT": 8332,
|
||||
"USERNAME": "mempool",
|
||||
"PASSWORD": "mempool",
|
||||
"TIMEOUT": 60000
|
||||
"TIMEOUT": 60000,
|
||||
"COOKIE": false,
|
||||
"COOKIE_PATH": "/path/to/bitcoin/.cookie"
|
||||
},
|
||||
"ELECTRUM": {
|
||||
"HOST": "127.0.0.1",
|
||||
|
@ -51,6 +53,8 @@
|
|||
"REST_API_URL": "http://127.0.0.1:3000",
|
||||
"UNIX_SOCKET_PATH": "/tmp/esplora-bitcoin-mainnet",
|
||||
"RETRY_UNIX_SOCKET_AFTER": 30000,
|
||||
"REQUEST_TIMEOUT": 10000,
|
||||
"FALLBACK_TIMEOUT": 5000,
|
||||
"FALLBACK": []
|
||||
},
|
||||
"SECOND_CORE_RPC": {
|
||||
|
@ -58,7 +62,9 @@
|
|||
"PORT": 8332,
|
||||
"USERNAME": "mempool",
|
||||
"PASSWORD": "mempool",
|
||||
"TIMEOUT": 60000
|
||||
"TIMEOUT": 60000,
|
||||
"COOKIE": false,
|
||||
"COOKIE_PATH": "/path/to/bitcoin/.cookie"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
|
|
586
backend/package-lock.json
generated
586
backend/package-lock.json
generated
|
@ -9,12 +9,12 @@
|
|||
"version": "3.0.0-dev",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^18.15.3",
|
||||
"axios": "~1.5.0",
|
||||
"axios": "~1.6.1",
|
||||
"bitcoinjs-lib": "~6.1.3",
|
||||
"crypto-js": "~4.1.1",
|
||||
"crypto-js": "~4.2.0",
|
||||
"express": "~4.18.2",
|
||||
"maxmind": "~4.3.11",
|
||||
"mysql2": "~3.6.0",
|
||||
|
@ -26,7 +26,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/express": "^4.17.17",
|
||||
|
@ -65,47 +65,48 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
|
||||
"integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.18.6"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz",
|
||||
"integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz",
|
||||
"integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz",
|
||||
"integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
|
||||
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-compilation-targets": "^7.21.4",
|
||||
"@babel/helper-module-transforms": "^7.21.2",
|
||||
"@babel/helpers": "^7.21.0",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-compilation-targets": "^7.22.15",
|
||||
"@babel/helper-module-transforms": "^7.23.0",
|
||||
"@babel/helpers": "^7.23.2",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.2",
|
||||
"semver": "^6.3.0"
|
||||
"json5": "^2.2.3",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
@ -115,13 +116,19 @@
|
|||
"url": "https://opencollective.com/babel"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz",
|
||||
"integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
|
@ -145,87 +152,84 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz",
|
||||
"integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
|
||||
"integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.21.4",
|
||||
"@babel/helper-validator-option": "^7.21.0",
|
||||
"browserslist": "^4.21.3",
|
||||
"@babel/compat-data": "^7.22.9",
|
||||
"@babel/helper-validator-option": "^7.22.15",
|
||||
"browserslist": "^4.21.9",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.0"
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
|
||||
"integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
|
||||
"integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
|
||||
"integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.4"
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
|
||||
"integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
|
||||
"integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.2",
|
||||
"@babel/types": "^7.21.2"
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-module-imports": "^7.22.15",
|
||||
"@babel/helper-simple-access": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/helper-validator-identifier": "^7.22.20"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
|
@ -238,78 +242,78 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
|
||||
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.20.2"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
|
||||
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz",
|
||||
"integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
|
||||
"integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz",
|
||||
"integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
|
||||
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.0",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -317,9 +321,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz",
|
||||
"integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
|
@ -506,33 +510,33 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
|
||||
"integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/parser": "^7.20.7",
|
||||
"@babel/types": "^7.20.7"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz",
|
||||
"integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.21.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
|
@ -541,13 +545,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz",
|
||||
"integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -2321,9 +2325,9 @@
|
|||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
|
||||
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
|
||||
"integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
@ -2590,9 +2594,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.21.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
|
||||
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
|
||||
"version": "4.22.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
||||
"integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2602,13 +2606,17 @@
|
|||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001449",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"node-releases": "^2.0.8",
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
"caniuse-lite": "^1.0.30001541",
|
||||
"electron-to-chromium": "^1.4.535",
|
||||
"node-releases": "^2.0.13",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
|
@ -2700,9 +2708,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001473",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
|
||||
"integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==",
|
||||
"version": "1.0.30001547",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz",
|
||||
"integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2892,9 +2900,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
|
||||
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
|
@ -3023,9 +3031,9 @@
|
|||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.348",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz",
|
||||
"integrity": "sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ==",
|
||||
"version": "1.4.551",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
|
||||
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
|
@ -6184,9 +6192,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
|
||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
|
@ -7399,9 +7407,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -7411,6 +7419,10 @@
|
|||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
|
@ -7418,7 +7430,7 @@
|
|||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist-lint": "cli.js"
|
||||
"update-browserslist-db": "cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"browserslist": ">= 4.21.0"
|
||||
|
@ -7683,50 +7695,59 @@
|
|||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
|
||||
"integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.18.6"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz",
|
||||
"integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz",
|
||||
"integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz",
|
||||
"integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
|
||||
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-compilation-targets": "^7.21.4",
|
||||
"@babel/helper-module-transforms": "^7.21.2",
|
||||
"@babel/helpers": "^7.21.0",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-compilation-targets": "^7.22.15",
|
||||
"@babel/helper-module-transforms": "^7.23.0",
|
||||
"@babel/helpers": "^7.23.2",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.2",
|
||||
"semver": "^6.3.0"
|
||||
"json5": "^2.2.3",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz",
|
||||
"integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
|
@ -7746,66 +7767,63 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-compilation-targets": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz",
|
||||
"integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
|
||||
"integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.21.4",
|
||||
"@babel/helper-validator-option": "^7.21.0",
|
||||
"browserslist": "^4.21.3",
|
||||
"@babel/compat-data": "^7.22.9",
|
||||
"@babel/helper-validator-option": "^7.22.15",
|
||||
"browserslist": "^4.21.9",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.0"
|
||||
"semver": "^6.3.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
|
||||
"integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
|
||||
"integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
|
||||
"integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.21.4"
|
||||
"@babel/types": "^7.22.15"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
|
||||
"integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
|
||||
"integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.2",
|
||||
"@babel/types": "^7.21.2"
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-module-imports": "^7.22.15",
|
||||
"@babel/helper-simple-access": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/helper-validator-identifier": "^7.22.20"
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
|
@ -7815,67 +7833,67 @@
|
|||
"dev": true
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
|
||||
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.2"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
|
||||
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz",
|
||||
"integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
|
||||
"integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz",
|
||||
"integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
|
||||
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.0",
|
||||
"@babel/types": "^7.21.0"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz",
|
||||
"integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/plugin-syntax-async-generators": {
|
||||
|
@ -8005,42 +8023,42 @@
|
|||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
|
||||
"integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/parser": "^7.20.7",
|
||||
"@babel/types": "^7.20.7"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz",
|
||||
"integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.21.4",
|
||||
"@babel/generator": "^7.21.4",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.21.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.21.4",
|
||||
"@babel/types": "^7.21.4",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz",
|
||||
"integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -9397,9 +9415,9 @@
|
|||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
|
||||
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
|
||||
"integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
@ -9615,15 +9633,15 @@
|
|||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.21.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
|
||||
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
|
||||
"version": "4.22.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
||||
"integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001449",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"node-releases": "^2.0.8",
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
"caniuse-lite": "^1.0.30001541",
|
||||
"electron-to-chromium": "^1.4.535",
|
||||
"node-releases": "^2.0.13",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
}
|
||||
},
|
||||
"bs-logger": {
|
||||
|
@ -9694,9 +9712,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001473",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
|
||||
"integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==",
|
||||
"version": "1.0.30001547",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz",
|
||||
"integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
|
@ -9832,9 +9850,9 @@
|
|||
}
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
|
||||
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
|
@ -9924,9 +9942,9 @@
|
|||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.348",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz",
|
||||
"integrity": "sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ==",
|
||||
"version": "1.4.551",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
|
||||
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==",
|
||||
"dev": true
|
||||
},
|
||||
"emittery": {
|
||||
|
@ -12280,9 +12298,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
|
||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-path": {
|
||||
|
@ -13118,9 +13136,9 @@
|
|||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
|
||||
},
|
||||
"update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escalade": "^3.1.1",
|
||||
|
|
|
@ -38,12 +38,12 @@
|
|||
"rust-build": "cd rust-gbt && npm run build-release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^18.15.3",
|
||||
"axios": "~1.5.0",
|
||||
"axios": "~1.6.1",
|
||||
"bitcoinjs-lib": "~6.1.3",
|
||||
"crypto-js": "~4.1.1",
|
||||
"crypto-js": "~4.2.0",
|
||||
"express": "~4.18.2",
|
||||
"maxmind": "~4.3.11",
|
||||
"mysql2": "~3.6.0",
|
||||
|
@ -55,7 +55,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/express": "^4.17.17",
|
||||
|
|
|
@ -41,7 +41,9 @@
|
|||
"PORT": 15,
|
||||
"USERNAME": "__CORE_RPC_USERNAME__",
|
||||
"PASSWORD": "__CORE_RPC_PASSWORD__",
|
||||
"TIMEOUT": 1000
|
||||
"TIMEOUT": 1000,
|
||||
"COOKIE": false,
|
||||
"COOKIE_PATH": "__CORE_RPC_COOKIE_PATH__"
|
||||
},
|
||||
"ELECTRUM": {
|
||||
"HOST": "__ELECTRUM_HOST__",
|
||||
|
@ -52,6 +54,8 @@
|
|||
"REST_API_URL": "__ESPLORA_REST_API_URL__",
|
||||
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
|
||||
"RETRY_UNIX_SOCKET_AFTER": 888,
|
||||
"REQUEST_TIMEOUT": 10000,
|
||||
"FALLBACK_TIMEOUT": 5000,
|
||||
"FALLBACK": []
|
||||
},
|
||||
"SECOND_CORE_RPC": {
|
||||
|
@ -59,7 +63,9 @@
|
|||
"PORT": 17,
|
||||
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
|
||||
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
|
||||
"TIMEOUT": 2000
|
||||
"TIMEOUT": 2000,
|
||||
"COOKIE": false,
|
||||
"COOKIE_PATH": "__SECOND_CORE_RPC_COOKIE_PATH__"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": false,
|
||||
|
|
|
@ -56,6 +56,8 @@ describe('Mempool Backend Config', () => {
|
|||
REST_API_URL: 'http://127.0.0.1:3000',
|
||||
UNIX_SOCKET_PATH: null,
|
||||
RETRY_UNIX_SOCKET_AFTER: 30000,
|
||||
REQUEST_TIMEOUT: 10000,
|
||||
FALLBACK_TIMEOUT: 5000,
|
||||
FALLBACK: [],
|
||||
});
|
||||
|
||||
|
@ -64,7 +66,9 @@ describe('Mempool Backend Config', () => {
|
|||
PORT: 8332,
|
||||
USERNAME: 'mempool',
|
||||
PASSWORD: 'mempool',
|
||||
TIMEOUT: 60000
|
||||
TIMEOUT: 60000,
|
||||
COOKIE: false,
|
||||
COOKIE_PATH: '/bitcoin/.cookie'
|
||||
});
|
||||
|
||||
expect(config.SECOND_CORE_RPC).toStrictEqual({
|
||||
|
@ -72,7 +76,9 @@ describe('Mempool Backend Config', () => {
|
|||
PORT: 8332,
|
||||
USERNAME: 'mempool',
|
||||
PASSWORD: 'mempool',
|
||||
TIMEOUT: 60000
|
||||
TIMEOUT: 60000,
|
||||
COOKIE: false,
|
||||
COOKIE_PATH: '/bitcoin/.cookie'
|
||||
});
|
||||
|
||||
expect(config.DATABASE).toStrictEqual({
|
||||
|
|
|
@ -3,6 +3,7 @@ import { IEsploraApi } from './esplora-api.interface';
|
|||
export interface AbstractBitcoinApi {
|
||||
$getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]>;
|
||||
$getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise<IEsploraApi.Transaction>;
|
||||
$getRawTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]>;
|
||||
$getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]>;
|
||||
$getAllMempoolTransactions(lastTxid: string);
|
||||
$getTransactionHex(txId: string): Promise<string>;
|
||||
|
@ -23,6 +24,8 @@ export interface AbstractBitcoinApi {
|
|||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend>;
|
||||
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]>;
|
||||
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
||||
$getBatchedOutspendsInternal(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
||||
$getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise<IEsploraApi.Outspend[]>;
|
||||
|
||||
startHealthChecks(): void;
|
||||
}
|
||||
|
@ -32,4 +35,5 @@ export interface BitcoinRpcCredentials {
|
|||
user: string;
|
||||
pass: string;
|
||||
timeout: number;
|
||||
cookie?: string;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,19 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||
});
|
||||
}
|
||||
|
||||
async $getRawTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
|
||||
const txs: IEsploraApi.Transaction[] = [];
|
||||
for (const txid of txids) {
|
||||
try {
|
||||
const tx = await this.$getRawTransaction(txid, false, true);
|
||||
txs.push(tx);
|
||||
} catch (err) {
|
||||
// skip failures
|
||||
}
|
||||
}
|
||||
return txs;
|
||||
}
|
||||
|
||||
$getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
|
||||
throw new Error('Method getMempoolTransactions not supported by the Bitcoin RPC API.');
|
||||
}
|
||||
|
@ -198,6 +211,19 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||
return outspends;
|
||||
}
|
||||
|
||||
async $getBatchedOutspendsInternal(txId: string[]): Promise<IEsploraApi.Outspend[][]> {
|
||||
return this.$getBatchedOutspends(txId);
|
||||
}
|
||||
|
||||
async $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise<IEsploraApi.Outspend[]> {
|
||||
const outspends: IEsploraApi.Outspend[] = [];
|
||||
for (const outpoint of outpoints) {
|
||||
const outspend = await this.$getOutspend(outpoint.txid, outpoint.vout);
|
||||
outspends.push(outspend);
|
||||
}
|
||||
return outspends;
|
||||
}
|
||||
|
||||
$getEstimatedHashrate(blockHeight: number): Promise<number> {
|
||||
// 120 is the default block span in Core
|
||||
return this.bitcoindClient.getNetworkHashPs(120, blockHeight);
|
||||
|
|
|
@ -8,6 +8,7 @@ const nodeRpcCredentials: BitcoinRpcCredentials = {
|
|||
user: config.CORE_RPC.USERNAME,
|
||||
pass: config.CORE_RPC.PASSWORD,
|
||||
timeout: config.CORE_RPC.TIMEOUT,
|
||||
cookie: config.CORE_RPC.COOKIE ? config.CORE_RPC.COOKIE_PATH : undefined,
|
||||
};
|
||||
|
||||
export default new bitcoin.Client(nodeRpcCredentials);
|
||||
|
|
|
@ -8,6 +8,7 @@ const nodeRpcCredentials: BitcoinRpcCredentials = {
|
|||
user: config.SECOND_CORE_RPC.USERNAME,
|
||||
pass: config.SECOND_CORE_RPC.PASSWORD,
|
||||
timeout: config.SECOND_CORE_RPC.TIMEOUT,
|
||||
cookie: config.SECOND_CORE_RPC.COOKIE ? config.SECOND_CORE_RPC.COOKIE_PATH : undefined,
|
||||
};
|
||||
|
||||
export default new bitcoin.Client(nodeRpcCredentials);
|
||||
|
|
|
@ -24,7 +24,6 @@ class BitcoinRoutes {
|
|||
public initRoutes(app: Application) {
|
||||
app
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'transaction-times', this.getTransactionTimes)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'outspends', this.$getBatchedOutspends)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'cpfp/:txId', this.$getCpfpInfo)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'difficulty-adjustment', this.getDifficultyChange)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', this.getRecommendedFees)
|
||||
|
@ -112,6 +111,7 @@ class BitcoinRoutes {
|
|||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', this.getTransactionStatus)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', this.getTransactionOutspends)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'txs/outspends', this.$getBatchedOutspends)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', this.getBlockHeader)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/hash', this.getBlockTipHash)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/raw', this.getRawBlock)
|
||||
|
@ -174,24 +174,20 @@ class BitcoinRoutes {
|
|||
res.json(times);
|
||||
}
|
||||
|
||||
private async $getBatchedOutspends(req: Request, res: Response) {
|
||||
if (!Array.isArray(req.query.txId)) {
|
||||
res.status(500).send('Not an array');
|
||||
private async $getBatchedOutspends(req: Request, res: Response): Promise<IEsploraApi.Outspend[][] | void> {
|
||||
const txids_csv = req.query.txids;
|
||||
if (!txids_csv || typeof txids_csv !== 'string') {
|
||||
res.status(500).send('Invalid txids format');
|
||||
return;
|
||||
}
|
||||
if (req.query.txId.length > 50) {
|
||||
const txids = txids_csv.split(',');
|
||||
if (txids.length > 50) {
|
||||
res.status(400).send('Too many txids requested');
|
||||
return;
|
||||
}
|
||||
const txIds: string[] = [];
|
||||
for (const _txId in req.query.txId) {
|
||||
if (typeof req.query.txId[_txId] === 'string') {
|
||||
txIds.push(req.query.txId[_txId].toString());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txIds);
|
||||
const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txids);
|
||||
res.json(batchedOutspends);
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
|
@ -251,7 +247,7 @@ class BitcoinRoutes {
|
|||
|
||||
private async getTransaction(req: Request, res: Response) {
|
||||
try {
|
||||
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true);
|
||||
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true, false, false, true);
|
||||
res.json(transaction);
|
||||
} catch (e) {
|
||||
let statusCode = 500;
|
||||
|
@ -478,7 +474,7 @@ class BitcoinRoutes {
|
|||
}
|
||||
|
||||
let nextHash = startFromHash;
|
||||
for (let i = 0; i < 10 && nextHash; i++) {
|
||||
for (let i = 0; i < 15 && nextHash; i++) {
|
||||
const localBlock = blocks.getBlocks().find((b) => b.id === nextHash);
|
||||
if (localBlock) {
|
||||
returnBlocks.push(localBlock);
|
||||
|
|
|
@ -6,6 +6,7 @@ export namespace IEsploraApi {
|
|||
size: number;
|
||||
weight: number;
|
||||
fee: number;
|
||||
sigops?: number;
|
||||
vin: Vin[];
|
||||
vout: Vout[];
|
||||
status: Status;
|
||||
|
|
|
@ -75,9 +75,9 @@ class FailoverRouter {
|
|||
|
||||
const results = await Promise.allSettled(this.hosts.map(async (host) => {
|
||||
if (host.socket) {
|
||||
return this.pollConnection.get<number>('/blocks/tip/height', { socketPath: host.host, timeout: 5000 });
|
||||
return this.pollConnection.get<number>('/blocks/tip/height', { socketPath: host.host, timeout: config.ESPLORA.FALLBACK_TIMEOUT });
|
||||
} else {
|
||||
return this.pollConnection.get<number>(host.host + '/blocks/tip/height', { timeout: 5000 });
|
||||
return this.pollConnection.get<number>(host.host + '/blocks/tip/height', { timeout: config.ESPLORA.FALLBACK_TIMEOUT });
|
||||
}
|
||||
}));
|
||||
const maxHeight = results.reduce((max, result) => Math.max(max, result.status === 'fulfilled' ? result.value?.data || 0 : 0), 0);
|
||||
|
@ -168,12 +168,15 @@ class FailoverRouter {
|
|||
let axiosConfig;
|
||||
let url;
|
||||
if (host.socket) {
|
||||
axiosConfig = { socketPath: host.host, timeout: 10000, responseType };
|
||||
axiosConfig = { socketPath: host.host, timeout: config.ESPLORA.REQUEST_TIMEOUT, responseType };
|
||||
url = path;
|
||||
} else {
|
||||
axiosConfig = { timeout: 10000, responseType };
|
||||
axiosConfig = { timeout: config.ESPLORA.REQUEST_TIMEOUT, responseType };
|
||||
url = host.host + path;
|
||||
}
|
||||
if (data?.params) {
|
||||
axiosConfig.params = data.params;
|
||||
}
|
||||
return (method === 'post'
|
||||
? this.requestConnection.post<T>(url, data, axiosConfig)
|
||||
: this.requestConnection.get<T>(url, axiosConfig)
|
||||
|
@ -181,7 +184,8 @@ class FailoverRouter {
|
|||
.catch((e) => {
|
||||
let fallbackHost = this.fallbackHost;
|
||||
if (e?.response?.status !== 404) {
|
||||
logger.warn(`esplora request failed ${e?.response?.status || 500} ${host.host}${path}`);
|
||||
logger.warn(`esplora request failed ${e?.response?.status} ${host.host}${path}`);
|
||||
logger.warn(e instanceof Error ? e.message : e);
|
||||
fallbackHost = this.addFailure(host);
|
||||
}
|
||||
if (retry && e?.code === 'ECONNREFUSED' && this.multihost) {
|
||||
|
@ -193,8 +197,8 @@ class FailoverRouter {
|
|||
});
|
||||
}
|
||||
|
||||
public async $get<T>(path, responseType = 'json'): Promise<T> {
|
||||
return this.$query<T>('get', path, null, responseType);
|
||||
public async $get<T>(path, responseType = 'json', params: any = null): Promise<T> {
|
||||
return this.$query<T>('get', path, params ? { params } : null, responseType);
|
||||
}
|
||||
|
||||
public async $post<T>(path, data: any, responseType = 'json'): Promise<T> {
|
||||
|
@ -213,12 +217,16 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||
return this.failoverRouter.$get<IEsploraApi.Transaction>('/tx/' + txId);
|
||||
}
|
||||
|
||||
async $getRawTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
|
||||
return this.failoverRouter.$post<IEsploraApi.Transaction[]>('/internal/txs', txids, 'json');
|
||||
}
|
||||
|
||||
async $getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
|
||||
return this.failoverRouter.$post<IEsploraApi.Transaction[]>('/mempool/txs', txids, 'json');
|
||||
return this.failoverRouter.$post<IEsploraApi.Transaction[]>('/internal/mempool/txs', txids, 'json');
|
||||
}
|
||||
|
||||
async $getAllMempoolTransactions(lastSeenTxid?: string): Promise<IEsploraApi.Transaction[]> {
|
||||
return this.failoverRouter.$get<IEsploraApi.Transaction[]>('/mempool/txs' + (lastSeenTxid ? '/' + lastSeenTxid : ''));
|
||||
return this.failoverRouter.$get<IEsploraApi.Transaction[]>('/internal/mempool/txs' + (lastSeenTxid ? '/' + lastSeenTxid : ''));
|
||||
}
|
||||
|
||||
$getTransactionHex(txId: string): Promise<string> {
|
||||
|
@ -238,7 +246,7 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||
}
|
||||
|
||||
$getTxsForBlock(hash: string): Promise<IEsploraApi.Transaction[]> {
|
||||
return this.failoverRouter.$get<IEsploraApi.Transaction[]>('/block/' + hash + '/txs');
|
||||
return this.failoverRouter.$get<IEsploraApi.Transaction[]>('/internal/block/' + hash + '/txs');
|
||||
}
|
||||
|
||||
$getBlockHash(height: number): Promise<string> {
|
||||
|
@ -290,13 +298,16 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||
return this.failoverRouter.$get<IEsploraApi.Outspend[]>('/tx/' + txId + '/outspends');
|
||||
}
|
||||
|
||||
async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> {
|
||||
const outspends: IEsploraApi.Outspend[][] = [];
|
||||
for (const tx of txId) {
|
||||
const outspend = await this.$getOutspends(tx);
|
||||
outspends.push(outspend);
|
||||
}
|
||||
return outspends;
|
||||
async $getBatchedOutspends(txids: string[]): Promise<IEsploraApi.Outspend[][]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
async $getBatchedOutspendsInternal(txids: string[]): Promise<IEsploraApi.Outspend[][]> {
|
||||
return this.failoverRouter.$post<IEsploraApi.Outspend[][]>('/internal/txs/outspends/by-txid', txids, 'json');
|
||||
}
|
||||
|
||||
async $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise<IEsploraApi.Outspend[]> {
|
||||
return this.failoverRouter.$post<IEsploraApi.Outspend[]>('/internal/txs/outspends/by-outpoint', outpoints.map(out => `${out.txid}:${out.vout}`), 'json');
|
||||
}
|
||||
|
||||
public startHealthChecks(): void {
|
||||
|
|
|
@ -81,6 +81,7 @@ class Blocks {
|
|||
private async $getTransactionsExtended(
|
||||
blockHash: string,
|
||||
blockHeight: number,
|
||||
blockTime: number,
|
||||
onlyCoinbase: boolean,
|
||||
txIds: string[] | null = null,
|
||||
quiet: boolean = false,
|
||||
|
@ -101,6 +102,12 @@ class Blocks {
|
|||
if (!onlyCoinbase) {
|
||||
for (const txid of txIds) {
|
||||
if (mempool[txid]) {
|
||||
mempool[txid].status = {
|
||||
confirmed: true,
|
||||
block_height: blockHeight,
|
||||
block_hash: blockHash,
|
||||
block_time: blockTime,
|
||||
};
|
||||
transactionMap[txid] = mempool[txid];
|
||||
foundInMempool++;
|
||||
totalFound++;
|
||||
|
@ -608,7 +615,7 @@ class Blocks {
|
|||
}
|
||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
||||
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, null, true);
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, block.timestamp, true, null, true);
|
||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||
|
||||
newlyIndexed++;
|
||||
|
@ -701,7 +708,7 @@ class Blocks {
|
|||
const verboseBlock = await bitcoinClient.getBlock(blockHash, 2);
|
||||
const block = BitcoinApi.convertBlock(verboseBlock);
|
||||
const txIds: string[] = verboseBlock.tx.map(tx => tx.txid);
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, false, txIds, false, true) as MempoolTransactionExtended[];
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, block.timestamp, false, txIds, false, true) as MempoolTransactionExtended[];
|
||||
|
||||
// fill in missing transaction fee data from verboseBlock
|
||||
for (let i = 0; i < transactions.length; i++) {
|
||||
|
@ -890,7 +897,7 @@ class Blocks {
|
|||
|
||||
const blockHash = await bitcoinApi.$getBlockHash(height);
|
||||
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, block.timestamp, true);
|
||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||
|
||||
if (Common.indexingEnabled()) {
|
||||
|
@ -902,7 +909,7 @@ class Blocks {
|
|||
|
||||
public async $indexStaleBlock(hash: string): Promise<BlockExtended> {
|
||||
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(hash);
|
||||
const transactions = await this.$getTransactionsExtended(hash, block.height, true);
|
||||
const transactions = await this.$getTransactionsExtended(hash, block.height, block.timestamp, true);
|
||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||
|
||||
blockExtended.canonical = await bitcoinApi.$getBlockHash(block.height);
|
||||
|
|
|
@ -252,7 +252,11 @@ class DiskCache {
|
|||
}
|
||||
|
||||
if (rbfData?.rbf) {
|
||||
rbfCache.load(rbfData.rbf);
|
||||
rbfCache.load({
|
||||
txs: rbfData.rbf.txs.map(([txid, entry]) => ({ value: entry })),
|
||||
trees: rbfData.rbf.trees,
|
||||
expiring: rbfData.rbf.expiring.map(([txid, value]) => ({ key: txid, value })),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Failed to parse rbf cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
|
|
|
@ -3,21 +3,30 @@ import { Common } from './common';
|
|||
import mempool from './mempool';
|
||||
import projectedBlocks from './mempool-blocks';
|
||||
|
||||
interface RecommendedFees {
|
||||
fastestFee: number,
|
||||
halfHourFee: number,
|
||||
hourFee: number,
|
||||
economyFee: number,
|
||||
minimumFee: number,
|
||||
}
|
||||
|
||||
class FeeApi {
|
||||
constructor() { }
|
||||
|
||||
defaultFee = Common.isLiquid() ? 0.1 : 1;
|
||||
|
||||
public getRecommendedFee() {
|
||||
public getRecommendedFee(): RecommendedFees {
|
||||
const pBlocks = projectedBlocks.getMempoolBlocks();
|
||||
const mPool = mempool.getMempoolInfo();
|
||||
const minimumFee = Math.ceil(mPool.mempoolminfee * 100000);
|
||||
const defaultMinFee = Math.max(minimumFee, this.defaultFee);
|
||||
|
||||
if (!pBlocks.length) {
|
||||
return {
|
||||
'fastestFee': this.defaultFee,
|
||||
'halfHourFee': this.defaultFee,
|
||||
'hourFee': this.defaultFee,
|
||||
'fastestFee': defaultMinFee,
|
||||
'halfHourFee': defaultMinFee,
|
||||
'hourFee': defaultMinFee,
|
||||
'economyFee': minimumFee,
|
||||
'minimumFee': minimumFee,
|
||||
};
|
||||
|
@ -27,11 +36,15 @@ class FeeApi {
|
|||
const secondMedianFee = pBlocks[1] ? this.optimizeMedianFee(pBlocks[1], pBlocks[2], firstMedianFee) : this.defaultFee;
|
||||
const thirdMedianFee = pBlocks[2] ? this.optimizeMedianFee(pBlocks[2], pBlocks[3], secondMedianFee) : this.defaultFee;
|
||||
|
||||
// explicitly enforce a minimum of ceil(mempoolminfee) on all recommendations.
|
||||
// simply rounding up recommended rates is insufficient, as the purging rate
|
||||
// can exceed the median rate of projected blocks in some extreme scenarios
|
||||
// (see https://bitcoin.stackexchange.com/a/120024)
|
||||
return {
|
||||
'fastestFee': firstMedianFee,
|
||||
'halfHourFee': secondMedianFee,
|
||||
'hourFee': thirdMedianFee,
|
||||
'economyFee': Math.min(2 * minimumFee, thirdMedianFee),
|
||||
'fastestFee': Math.max(minimumFee, firstMedianFee),
|
||||
'halfHourFee': Math.max(minimumFee, secondMedianFee),
|
||||
'hourFee': Math.max(minimumFee, thirdMedianFee),
|
||||
'economyFee': Math.max(minimumFee, Math.min(2 * minimumFee, thirdMedianFee)),
|
||||
'minimumFee': minimumFee,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class MemoryCache {
|
|||
}
|
||||
|
||||
private cleanup() {
|
||||
this.cache = this.cache.filter((cache) => cache.expires < (new Date()));
|
||||
this.cache = this.cache.filter((cache) => cache.expires > (new Date()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ class Mempool {
|
|||
logger.debug(`Migrating ${Object.keys(this.mempoolCache).length} transactions from disk cache to Redis cache`);
|
||||
}
|
||||
for (const txid of Object.keys(this.mempoolCache)) {
|
||||
if (!this.mempoolCache[txid].sigops || this.mempoolCache[txid].effectiveFeePerVsize == null) {
|
||||
if (!this.mempoolCache[txid].adjustedVsize || this.mempoolCache[txid].sigops == null || this.mempoolCache[txid].effectiveFeePerVsize == null) {
|
||||
this.mempoolCache[txid] = transactionUtils.extendMempoolTransaction(this.mempoolCache[txid]);
|
||||
}
|
||||
if (this.mempoolCache[txid].order == null) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import config from "../config";
|
|||
import logger from "../logger";
|
||||
import { MempoolTransactionExtended, TransactionStripped } from "../mempool.interfaces";
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { IEsploraApi } from "./bitcoin/esplora-api.interface";
|
||||
import { Common } from "./common";
|
||||
import redisCache from "./redis-cache";
|
||||
|
||||
|
@ -53,6 +54,9 @@ class RbfCache {
|
|||
private expiring: Map<string, number> = new Map();
|
||||
private cacheQueue: CacheEvent[] = [];
|
||||
|
||||
private evictionCount = 0;
|
||||
private staleCount = 0;
|
||||
|
||||
constructor() {
|
||||
setInterval(this.cleanup.bind(this), 1000 * 60 * 10);
|
||||
}
|
||||
|
@ -245,6 +249,7 @@ class RbfCache {
|
|||
|
||||
// flag a transaction as removed from the mempool
|
||||
public evict(txid: string, fast: boolean = false): void {
|
||||
this.evictionCount++;
|
||||
if (this.txs.has(txid) && (fast || !this.expiring.has(txid))) {
|
||||
const expiryTime = fast ? Date.now() + (1000 * 60 * 10) : Date.now() + (1000 * 86400); // 24 hours
|
||||
this.addExpiration(txid, expiryTime);
|
||||
|
@ -272,18 +277,23 @@ class RbfCache {
|
|||
this.remove(txid);
|
||||
}
|
||||
}
|
||||
logger.debug(`rbf cache contains ${this.txs.size} txs, ${this.rbfTrees.size} trees, ${this.expiring.size} due to expire`);
|
||||
logger.debug(`rbf cache contains ${this.txs.size} txs, ${this.rbfTrees.size} trees, ${this.expiring.size} due to expire (${this.evictionCount} newly expired)`);
|
||||
this.evictionCount = 0;
|
||||
}
|
||||
|
||||
// remove a transaction & all previous versions from the cache
|
||||
private remove(txid): void {
|
||||
// don't remove a transaction if a newer version remains in the mempool
|
||||
if (!this.replacedBy.has(txid)) {
|
||||
const root = this.treeMap.get(txid);
|
||||
const replaces = this.replaces.get(txid);
|
||||
this.replaces.delete(txid);
|
||||
this.treeMap.delete(txid);
|
||||
this.removeTx(txid);
|
||||
this.removeExpiration(txid);
|
||||
if (root === txid) {
|
||||
this.removeTree(txid);
|
||||
}
|
||||
for (const tx of (replaces || [])) {
|
||||
// recursively remove prior versions from the cache
|
||||
this.replacedBy.delete(tx);
|
||||
|
@ -359,18 +369,27 @@ class RbfCache {
|
|||
}
|
||||
|
||||
public async load({ txs, trees, expiring }): Promise<void> {
|
||||
txs.forEach(txEntry => {
|
||||
this.txs.set(txEntry.key, txEntry.value);
|
||||
});
|
||||
for (const deflatedTree of trees) {
|
||||
await this.importTree(deflatedTree.root, deflatedTree.root, deflatedTree, this.txs);
|
||||
}
|
||||
expiring.forEach(expiringEntry => {
|
||||
if (this.txs.has(expiringEntry.key)) {
|
||||
this.expiring.set(expiringEntry.key, new Date(expiringEntry.value).getTime());
|
||||
try {
|
||||
txs.forEach(txEntry => {
|
||||
this.txs.set(txEntry.value.txid, txEntry.value);
|
||||
});
|
||||
this.staleCount = 0;
|
||||
for (const deflatedTree of trees) {
|
||||
await this.importTree(deflatedTree.root, deflatedTree.root, deflatedTree, this.txs);
|
||||
}
|
||||
});
|
||||
this.cleanup();
|
||||
expiring.forEach(expiringEntry => {
|
||||
if (this.txs.has(expiringEntry.key)) {
|
||||
this.expiring.set(expiringEntry.key, new Date(expiringEntry.value).getTime());
|
||||
}
|
||||
});
|
||||
this.staleCount = 0;
|
||||
await this.checkTrees();
|
||||
logger.debug(`loaded ${txs.length} txs, ${trees.length} trees into rbf cache, ${expiring.length} due to expire, ${this.staleCount} were stale`);
|
||||
this.cleanup();
|
||||
|
||||
} catch (e) {
|
||||
logger.err('failed to restore RBF cache: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
exportTree(tree: RbfTree, deflated: any = null) {
|
||||
|
@ -398,29 +417,11 @@ class RbfCache {
|
|||
const treeInfo = deflated[txid];
|
||||
const replaces: RbfTree[] = [];
|
||||
|
||||
// check if any transactions in this tree have already been confirmed
|
||||
mined = mined || treeInfo.mined;
|
||||
let exists = mined;
|
||||
if (!mined) {
|
||||
try {
|
||||
const apiTx = await bitcoinApi.$getRawTransaction(txid);
|
||||
if (apiTx) {
|
||||
exists = true;
|
||||
}
|
||||
if (apiTx?.status?.confirmed) {
|
||||
mined = true;
|
||||
treeInfo.txMined = true;
|
||||
this.evict(txid, true);
|
||||
}
|
||||
} catch (e) {
|
||||
// most transactions do not exist
|
||||
}
|
||||
}
|
||||
|
||||
// if the root tx is not in the mempool or the blockchain
|
||||
// evict this tree as soon as possible
|
||||
if (root === txid && !exists) {
|
||||
this.evict(txid, true);
|
||||
// if the root tx is unknown, remove this tree and return early
|
||||
if (root === txid && !txs.has(txid)) {
|
||||
this.staleCount++;
|
||||
this.removeTree(deflated.key);
|
||||
return;
|
||||
}
|
||||
|
||||
// recursively reconstruct child trees
|
||||
|
@ -458,6 +459,59 @@ class RbfCache {
|
|||
return tree;
|
||||
}
|
||||
|
||||
private async checkTrees(): Promise<void> {
|
||||
const found: { [txid: string]: boolean } = {};
|
||||
const txids = Array.from(this.txs.values()).map(tx => tx.txid).filter(txid => {
|
||||
return !this.expiring.has(txid) && !this.getRbfTree(txid)?.mined;
|
||||
});
|
||||
|
||||
const processTxs = (txs: IEsploraApi.Transaction[]): void => {
|
||||
for (const tx of txs) {
|
||||
found[tx.txid] = true;
|
||||
if (tx.status?.confirmed) {
|
||||
const tree = this.getRbfTree(tx.txid);
|
||||
if (tree) {
|
||||
this.setTreeMined(tree, tx.txid);
|
||||
tree.mined = true;
|
||||
this.evict(tx.txid, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
const sliceLength = 250;
|
||||
for (let i = 0; i < Math.ceil(txids.length / sliceLength); i++) {
|
||||
const slice = txids.slice(i * sliceLength, (i + 1) * sliceLength);
|
||||
try {
|
||||
const txs = await bitcoinApi.$getRawTransactions(slice);
|
||||
logger.debug(`fetched ${slice.length} cached rbf transactions`);
|
||||
processTxs(txs);
|
||||
logger.debug(`processed ${slice.length} cached rbf transactions`);
|
||||
} catch (err) {
|
||||
logger.err(`failed to fetch or process ${slice.length} cached rbf transactions`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const txs: IEsploraApi.Transaction[] = [];
|
||||
for (const txid of txids) {
|
||||
try {
|
||||
const tx = await bitcoinApi.$getRawTransaction(txid, true, false);
|
||||
txs.push(tx);
|
||||
} catch (err) {
|
||||
// some 404s are expected, so continue quietly
|
||||
}
|
||||
}
|
||||
processTxs(txs);
|
||||
}
|
||||
|
||||
for (const txid of txids) {
|
||||
if (!found[txid]) {
|
||||
this.evict(txid, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getLatestRbfSummary(): ReplacementInfo[] {
|
||||
const rbfList = this.getRbfTrees(false);
|
||||
return rbfList.slice(0, 6).map(rbfTree => {
|
||||
|
|
|
@ -219,7 +219,7 @@ class RedisCache {
|
|||
await memPool.$setMempool(loadedMempool);
|
||||
await rbfCache.load({
|
||||
txs: rbfTxs,
|
||||
trees: rbfTrees.map(loadedTree => loadedTree.value),
|
||||
trees: rbfTrees.map(loadedTree => { loadedTree.value.key = loadedTree.key; return loadedTree.value; }),
|
||||
expiring: rbfExpirations,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ class TransactionUtils {
|
|||
public extendMempoolTransaction(transaction: IEsploraApi.Transaction): MempoolTransactionExtended {
|
||||
const vsize = Math.ceil(transaction.weight / 4);
|
||||
const fractionalVsize = (transaction.weight / 4);
|
||||
const sigops = !Common.isLiquid() ? this.countSigops(transaction) : 0;
|
||||
let sigops = Common.isLiquid() ? 0 : (transaction.sigops != null ? transaction.sigops : this.countSigops(transaction));
|
||||
// https://github.com/bitcoin/bitcoin/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/policy/policy.cpp#L295-L298
|
||||
const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor
|
||||
const feePerVbytes = (transaction.fee || 0) / fractionalVsize;
|
||||
|
@ -155,7 +155,7 @@ class TransactionUtils {
|
|||
sigops += 20 * (script.match(/OP_CHECKMULTISIG/g)?.length || 0);
|
||||
} else {
|
||||
// in redeem scripts and witnesses, worth N if preceded by OP_N, 20 otherwise
|
||||
const matches = script.matchAll(/(?:OP_(\d+))? OP_CHECKMULTISIG/g);
|
||||
const matches = script.matchAll(/(?:OP_(?:PUSHNUM_)?(\d+))? OP_CHECKMULTISIG/g);
|
||||
for (const match of matches) {
|
||||
const n = parseInt(match[1]);
|
||||
if (Number.isInteger(n)) {
|
||||
|
@ -189,6 +189,12 @@ class TransactionUtils {
|
|||
sigops += this.countScriptSigops(bitcoinjs.script.toASM(Buffer.from(input.witness[input.witness.length - 1], 'hex')), false, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case input.prevout.scriptpubkey_type === 'p2sh':
|
||||
if (input.inner_redeemscript_asm) {
|
||||
sigops += this.countScriptSigops(input.inner_redeemscript_asm);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -577,7 +577,7 @@ class WebsocketHandler {
|
|||
response['utxoSpent'] = JSON.stringify(outspends);
|
||||
}
|
||||
|
||||
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
|
||||
const rbfReplacedBy = rbfChanges.map[client['track-tx']] ? rbfCache.getReplacedBy(client['track-tx']) : false;
|
||||
if (rbfReplacedBy) {
|
||||
response['rbfTransaction'] = JSON.stringify({
|
||||
txid: rbfReplacedBy,
|
||||
|
|
|
@ -44,6 +44,8 @@ interface IConfig {
|
|||
REST_API_URL: string;
|
||||
UNIX_SOCKET_PATH: string | void | null;
|
||||
RETRY_UNIX_SOCKET_AFTER: number;
|
||||
REQUEST_TIMEOUT: number;
|
||||
FALLBACK_TIMEOUT: number;
|
||||
FALLBACK: string[];
|
||||
};
|
||||
LIGHTNING: {
|
||||
|
@ -76,6 +78,8 @@ interface IConfig {
|
|||
USERNAME: string;
|
||||
PASSWORD: string;
|
||||
TIMEOUT: number;
|
||||
COOKIE: boolean;
|
||||
COOKIE_PATH: string;
|
||||
};
|
||||
SECOND_CORE_RPC: {
|
||||
HOST: string;
|
||||
|
@ -83,6 +87,8 @@ interface IConfig {
|
|||
USERNAME: string;
|
||||
PASSWORD: string;
|
||||
TIMEOUT: number;
|
||||
COOKIE: boolean;
|
||||
COOKIE_PATH: string;
|
||||
};
|
||||
DATABASE: {
|
||||
ENABLED: boolean;
|
||||
|
@ -190,6 +196,8 @@ const defaults: IConfig = {
|
|||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
'UNIX_SOCKET_PATH': null,
|
||||
'RETRY_UNIX_SOCKET_AFTER': 30000,
|
||||
'REQUEST_TIMEOUT': 10000,
|
||||
'FALLBACK_TIMEOUT': 5000,
|
||||
'FALLBACK': [],
|
||||
},
|
||||
'ELECTRUM': {
|
||||
|
@ -203,6 +211,8 @@ const defaults: IConfig = {
|
|||
'USERNAME': 'mempool',
|
||||
'PASSWORD': 'mempool',
|
||||
'TIMEOUT': 60000,
|
||||
'COOKIE': false,
|
||||
'COOKIE_PATH': '/bitcoin/.cookie'
|
||||
},
|
||||
'SECOND_CORE_RPC': {
|
||||
'HOST': '127.0.0.1',
|
||||
|
@ -210,6 +220,8 @@ const defaults: IConfig = {
|
|||
'USERNAME': 'mempool',
|
||||
'PASSWORD': 'mempool',
|
||||
'TIMEOUT': 60000,
|
||||
'COOKIE': false,
|
||||
'COOKIE_PATH': '/bitcoin/.cookie'
|
||||
},
|
||||
'DATABASE': {
|
||||
'ENABLED': true,
|
||||
|
|
|
@ -4,6 +4,7 @@ import config from './config';
|
|||
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
|
||||
import logger from './logger';
|
||||
import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } from 'mysql2/typings/mysql';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
class DB {
|
||||
constructor() {
|
||||
|
@ -105,26 +106,43 @@ import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } fr
|
|||
|
||||
public getPidLock(): boolean {
|
||||
const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`);
|
||||
this.enforcePidLock(filePath);
|
||||
fs.writeFileSync(filePath, `${process.pid}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
private enforcePidLock(filePath: string): void {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const pid = fs.readFileSync(filePath).toString();
|
||||
if (pid !== `${process.pid}`) {
|
||||
const msg = `Already running on PID ${pid} (or pid file '${filePath}' is stale)`;
|
||||
const pid = parseInt(fs.readFileSync(filePath, 'utf-8'));
|
||||
if (pid === process.pid) {
|
||||
logger.warn('PID file already exists for this process');
|
||||
return;
|
||||
}
|
||||
|
||||
let cmd;
|
||||
try {
|
||||
cmd = execSync(`ps -p ${pid} -o args=`);
|
||||
} catch (e) {
|
||||
logger.warn(`Stale PID file at ${filePath}, but no process running on that PID ${pid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd && cmd.toString()?.includes('node')) {
|
||||
const msg = `Another mempool nodejs process is already running on PID ${pid}`;
|
||||
logger.err(msg);
|
||||
throw new Error(msg);
|
||||
} else {
|
||||
return true;
|
||||
logger.warn(`Stale PID file at ${filePath}, but the PID ${pid} does not belong to a running mempool instance`);
|
||||
}
|
||||
} else {
|
||||
fs.writeFileSync(filePath, `${process.pid}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public releasePidLock(): void {
|
||||
const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const pid = fs.readFileSync(filePath).toString();
|
||||
if (pid === `${process.pid}`) {
|
||||
const pid = parseInt(fs.readFileSync(filePath, 'utf-8'));
|
||||
// only release our own pid file
|
||||
if (pid === process.pid) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ class Server {
|
|||
logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`);
|
||||
|
||||
// Register cleanup listeners for exit events
|
||||
['exit', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'unhandledRejection'].forEach(event => {
|
||||
['exit', 'SIGHUP', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'unhandledRejection'].forEach(event => {
|
||||
process.on(event, () => { this.onExit(event); });
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var http = require('http')
|
||||
var https = require('https')
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
var JsonRPC = function (opts) {
|
||||
// @ts-ignore
|
||||
|
@ -55,7 +56,13 @@ JsonRPC.prototype.call = function (method, params) {
|
|||
}
|
||||
|
||||
// use HTTP auth if user and password set
|
||||
if (this.opts.user && this.opts.pass) {
|
||||
if (this.opts.cookie) {
|
||||
if (!this.cachedCookie) {
|
||||
this.cachedCookie = readFileSync(this.opts.cookie).toString();
|
||||
}
|
||||
// @ts-ignore
|
||||
requestOptions.auth = this.cachedCookie;
|
||||
} else if (this.opts.user && this.opts.pass) {
|
||||
// @ts-ignore
|
||||
requestOptions.auth = this.opts.user + ':' + this.opts.pass
|
||||
}
|
||||
|
@ -93,7 +100,7 @@ JsonRPC.prototype.call = function (method, params) {
|
|||
reject(err)
|
||||
})
|
||||
|
||||
request.on('response', function (response) {
|
||||
request.on('response', (response) => {
|
||||
clearTimeout(reqTimeout)
|
||||
|
||||
// We need to buffer the response chunks in a nonblocking way.
|
||||
|
@ -104,7 +111,7 @@ JsonRPC.prototype.call = function (method, params) {
|
|||
// When all the responses are finished, we decode the JSON and
|
||||
// depending on whether it's got a result or an error, we call
|
||||
// emitSuccess or emitError on the promise.
|
||||
response.on('end', function () {
|
||||
response.on('end', () => {
|
||||
var err
|
||||
|
||||
if (cbCalled) return
|
||||
|
@ -113,6 +120,14 @@ JsonRPC.prototype.call = function (method, params) {
|
|||
try {
|
||||
var decoded = JSON.parse(buffer)
|
||||
} catch (e) {
|
||||
// if we authenticated using a cookie and it failed, read the cookie file again
|
||||
if (
|
||||
response.statusCode === 401 /* Unauthorized */ &&
|
||||
this.opts.cookie
|
||||
) {
|
||||
this.cachedCookie = undefined;
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
err = new Error('Invalid params, response status code: ' + response.statusCode)
|
||||
err.code = -32602
|
||||
|
|
|
@ -15,8 +15,6 @@ class ForensicsService {
|
|||
txCache: { [txid: string]: IEsploraApi.Transaction } = {};
|
||||
tempCached: string[] = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
public async $startService(): Promise<void> {
|
||||
logger.info('Starting lightning network forensics service');
|
||||
|
||||
|
@ -66,93 +64,138 @@ class ForensicsService {
|
|||
*/
|
||||
|
||||
public async $runClosedChannelsForensics(onlyNewChannels: boolean = false): Promise<void> {
|
||||
// Only Esplora backend can retrieve spent transaction outputs
|
||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||
return;
|
||||
}
|
||||
|
||||
let progress = 0;
|
||||
|
||||
try {
|
||||
logger.debug(`Started running closed channel forensics...`);
|
||||
let channels;
|
||||
let allChannels;
|
||||
if (onlyNewChannels) {
|
||||
channels = await channelsApi.$getClosedChannelsWithoutReason();
|
||||
allChannels = await channelsApi.$getClosedChannelsWithoutReason();
|
||||
} else {
|
||||
channels = await channelsApi.$getUnresolvedClosedChannels();
|
||||
allChannels = await channelsApi.$getUnresolvedClosedChannels();
|
||||
}
|
||||
|
||||
for (const channel of channels) {
|
||||
let reason = 0;
|
||||
let resolvedForceClose = false;
|
||||
// Only Esplora backend can retrieve spent transaction outputs
|
||||
const cached: string[] = [];
|
||||
let progress = 0;
|
||||
const sliceLength = 1000;
|
||||
// process batches of 1000 channels
|
||||
for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) {
|
||||
const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength);
|
||||
|
||||
let allOutspends: IEsploraApi.Outspend[][] = [];
|
||||
const forceClosedChannels: { channel: any, cachedSpends: string[] }[] = [];
|
||||
|
||||
// fetch outspends in bulk
|
||||
try {
|
||||
let outspends: IEsploraApi.Outspend[] | undefined;
|
||||
try {
|
||||
outspends = await bitcoinApi.$getOutspends(channel.closing_transaction_id);
|
||||
await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT);
|
||||
} catch (e) {
|
||||
logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + channel.closing_transaction_id + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`);
|
||||
continue;
|
||||
}
|
||||
const lightningScriptReasons: number[] = [];
|
||||
const outspendTxids = channels.map(channel => channel.closing_transaction_id);
|
||||
allOutspends = await bitcoinApi.$getBatchedOutspendsInternal(outspendTxids);
|
||||
logger.info(`Fetched outspends for ${allOutspends.length} txs from esplora for LN forensics`);
|
||||
await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT);
|
||||
} catch (e) {
|
||||
logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/internal/txs/outspends/by-txid'}. Reason ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
// fetch spending transactions in bulk and load into txCache
|
||||
const newSpendingTxids: { [txid: string]: boolean } = {};
|
||||
for (const outspends of allOutspends) {
|
||||
for (const outspend of outspends) {
|
||||
if (outspend.spent && outspend.txid) {
|
||||
let spendingTx = await this.fetchTransaction(outspend.txid);
|
||||
if (!spendingTx) {
|
||||
continue;
|
||||
}
|
||||
cached.push(spendingTx.txid);
|
||||
const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]);
|
||||
lightningScriptReasons.push(lightningScript);
|
||||
newSpendingTxids[outspend.txid] = true;
|
||||
}
|
||||
}
|
||||
const filteredReasons = lightningScriptReasons.filter((r) => r !== 1);
|
||||
if (filteredReasons.length) {
|
||||
if (filteredReasons.some((r) => r === 2 || r === 4)) {
|
||||
reason = 3;
|
||||
} else {
|
||||
reason = 2;
|
||||
resolvedForceClose = true;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
We can detect a commitment transaction (force close) by reading Sequence and Locktime
|
||||
https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction
|
||||
*/
|
||||
let closingTx = await this.fetchTransaction(channel.closing_transaction_id, true);
|
||||
if (!closingTx) {
|
||||
}
|
||||
const allOutspendTxs = await this.fetchTransactions(
|
||||
allOutspends.flatMap(outspends =>
|
||||
outspends
|
||||
.filter(outspend => outspend.spent && outspend.txid)
|
||||
.map(outspend => outspend.txid)
|
||||
)
|
||||
);
|
||||
logger.info(`Fetched ${allOutspendTxs.length} out-spending txs from esplora for LN forensics`);
|
||||
|
||||
// process each outspend
|
||||
for (const [index, channel] of channels.entries()) {
|
||||
let reason = 0;
|
||||
const cached: string[] = [];
|
||||
try {
|
||||
const outspends = allOutspends[index];
|
||||
if (!outspends || !outspends.length) {
|
||||
// outspends are missing
|
||||
continue;
|
||||
}
|
||||
cached.push(closingTx.txid);
|
||||
const sequenceHex: string = closingTx.vin[0].sequence.toString(16);
|
||||
const locktimeHex: string = closingTx.locktime.toString(16);
|
||||
if (sequenceHex.substring(0, 2) === '80' && locktimeHex.substring(0, 2) === '20') {
|
||||
reason = 2; // Here we can't be sure if it's a penalty or not
|
||||
} else {
|
||||
reason = 1;
|
||||
const lightningScriptReasons: number[] = [];
|
||||
for (const outspend of outspends) {
|
||||
if (outspend.spent && outspend.txid) {
|
||||
const spendingTx = this.txCache[outspend.txid];
|
||||
if (!spendingTx) {
|
||||
continue;
|
||||
}
|
||||
cached.push(spendingTx.txid);
|
||||
const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]);
|
||||
lightningScriptReasons.push(lightningScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reason) {
|
||||
logger.debug('Setting closing reason ' + reason + ' for channel: ' + channel.id + '.');
|
||||
await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]);
|
||||
if (reason === 2 && resolvedForceClose) {
|
||||
await DB.query(`UPDATE channels SET closing_resolved = ? WHERE id = ?`, [true, channel.id]);
|
||||
}
|
||||
if (reason !== 2 || resolvedForceClose) {
|
||||
const filteredReasons = lightningScriptReasons.filter((r) => r !== 1);
|
||||
if (filteredReasons.length) {
|
||||
if (filteredReasons.some((r) => r === 2 || r === 4)) {
|
||||
// Force closed with penalty
|
||||
reason = 3;
|
||||
} else {
|
||||
// Force closed without penalty
|
||||
reason = 2;
|
||||
await DB.query(`UPDATE channels SET closing_resolved = ? WHERE id = ?`, [true, channel.id]);
|
||||
}
|
||||
await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]);
|
||||
// clean up cached transactions
|
||||
cached.forEach(txid => {
|
||||
delete this.txCache[txid];
|
||||
});
|
||||
} else {
|
||||
forceClosedChannels.push({ channel, cachedSpends: cached });
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`$runClosedChannelsForensics() failed for channel ${channel.short_id}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`$runClosedChannelsForensics() failed for channel ${channel.short_id}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
|
||||
++progress;
|
||||
// fetch force-closing transactions in bulk
|
||||
const closingTxs = await this.fetchTransactions(forceClosedChannels.map(x => x.channel.closing_transaction_id));
|
||||
logger.info(`Fetched ${closingTxs.length} closing txs from esplora for LN forensics`);
|
||||
|
||||
// process channels with no lightning script reasons
|
||||
for (const { channel, cachedSpends } of forceClosedChannels) {
|
||||
const closingTx = this.txCache[channel.closing_transaction_id];
|
||||
if (!closingTx) {
|
||||
// no channel close transaction found yet
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
We can detect a commitment transaction (force close) by reading Sequence and Locktime
|
||||
https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction
|
||||
*/
|
||||
const sequenceHex: string = closingTx.vin[0].sequence.toString(16);
|
||||
const locktimeHex: string = closingTx.locktime.toString(16);
|
||||
let reason;
|
||||
if (sequenceHex.substring(0, 2) === '80' && locktimeHex.substring(0, 2) === '20') {
|
||||
// Force closed, but we can't be sure if it's a penalty or not
|
||||
reason = 2;
|
||||
} else {
|
||||
// Mutually closed
|
||||
reason = 1;
|
||||
// clean up cached transactions
|
||||
delete this.txCache[closingTx.txid];
|
||||
for (const txid of cachedSpends) {
|
||||
delete this.txCache[txid];
|
||||
}
|
||||
}
|
||||
await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]);
|
||||
}
|
||||
|
||||
progress += channels.length;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.debug(`Updating channel closed channel forensics ${progress}/${channels.length}`);
|
||||
logger.debug(`Updating channel closed channel forensics ${progress}/${allChannels.length}`);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
@ -220,8 +263,11 @@ class ForensicsService {
|
|||
logger.debug(`Started running open channel forensics...`);
|
||||
const channels = await channelsApi.$getChannelsWithoutSourceChecked();
|
||||
|
||||
// preload open channel transactions
|
||||
await this.fetchTransactions(channels.map(channel => channel.transaction_id), true);
|
||||
|
||||
for (const openChannel of channels) {
|
||||
let openTx = await this.fetchTransaction(openChannel.transaction_id, true);
|
||||
const openTx = this.txCache[openChannel.transaction_id];
|
||||
if (!openTx) {
|
||||
continue;
|
||||
}
|
||||
|
@ -276,7 +322,7 @@ class ForensicsService {
|
|||
|
||||
// Check if a channel open tx input spends the result of a swept channel close output
|
||||
private async $attributeSweptChannelCloses(openChannel: ILightningApi.Channel, input: IEsploraApi.Vin): Promise<void> {
|
||||
let sweepTx = await this.fetchTransaction(input.txid, true);
|
||||
const sweepTx = await this.fetchTransaction(input.txid, true);
|
||||
if (!sweepTx) {
|
||||
logger.err(`couldn't find input transaction for channel forensics ${openChannel.channel_id} ${input.txid}`);
|
||||
return;
|
||||
|
@ -335,7 +381,7 @@ class ForensicsService {
|
|||
|
||||
if (matched && !ambiguous) {
|
||||
// fetch closing channel transaction and perform forensics on the outputs
|
||||
let prevChannelTx = await this.fetchTransaction(input.txid, true);
|
||||
const prevChannelTx = await this.fetchTransaction(input.txid, true);
|
||||
let outspends: IEsploraApi.Outspend[] | undefined;
|
||||
try {
|
||||
outspends = await bitcoinApi.$getOutspends(input.txid);
|
||||
|
@ -355,17 +401,17 @@ class ForensicsService {
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
// preload outspend transactions
|
||||
await this.fetchTransactions(outspends.filter(o => o.spent && o.txid).map(o => o.txid), true);
|
||||
|
||||
for (let i = 0; i < outspends?.length; i++) {
|
||||
const outspend = outspends[i];
|
||||
const output = prevChannel.outputs[i];
|
||||
if (outspend.spent && outspend.txid) {
|
||||
try {
|
||||
const spendingTx = await this.fetchTransaction(outspend.txid, true);
|
||||
if (spendingTx) {
|
||||
output.type = this.findLightningScript(spendingTx.vin[outspend.vin || 0]);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + outspend.txid}. Reason ${e instanceof Error ? e.message : e}`);
|
||||
const spendingTx = this.txCache[outspend.txid];
|
||||
if (spendingTx) {
|
||||
output.type = this.findLightningScript(spendingTx.vin[outspend.vin || 0]);
|
||||
}
|
||||
} else {
|
||||
output.type = 0;
|
||||
|
@ -430,13 +476,36 @@ class ForensicsService {
|
|||
}
|
||||
await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT);
|
||||
} catch (e) {
|
||||
logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + txid + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`);
|
||||
logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + txid}. Reason ${e instanceof Error ? e.message : e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
// fetches a batch of transactions and adds them to the txCache
|
||||
// the returned list of txs does *not* preserve ordering or number
|
||||
async fetchTransactions(txids, temp: boolean = false): Promise<(IEsploraApi.Transaction | null)[]> {
|
||||
// deduplicate txids
|
||||
const uniqueTxids = [...new Set<string>(txids)];
|
||||
// filter out any transactions we already have in the cache
|
||||
const needToFetch: string[] = uniqueTxids.filter(txid => !this.txCache[txid]);
|
||||
try {
|
||||
const txs = await bitcoinApi.$getRawTransactions(needToFetch);
|
||||
for (const tx of txs) {
|
||||
this.txCache[tx.txid] = tx;
|
||||
if (temp) {
|
||||
this.tempCached.push(tx.txid);
|
||||
}
|
||||
}
|
||||
await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT);
|
||||
} catch (e) {
|
||||
logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs'}. Reason ${e instanceof Error ? e.message : e}`);
|
||||
return [];
|
||||
}
|
||||
return txids.map(txid => this.txCache[txid]);
|
||||
}
|
||||
|
||||
clearTempCache(): void {
|
||||
for (const txid of this.tempCached) {
|
||||
delete this.txCache[txid];
|
||||
|
|
|
@ -288,22 +288,32 @@ class NetworkSyncService {
|
|||
}
|
||||
logger.debug(`${log}`, logger.tags.ln);
|
||||
|
||||
const channels = await channelsApi.$getChannelsByStatus([0, 1]);
|
||||
for (const channel of channels) {
|
||||
const spendingTx = await bitcoinApi.$getOutspend(channel.transaction_id, channel.transaction_vout);
|
||||
if (spendingTx.spent === true && spendingTx.status?.confirmed === true) {
|
||||
logger.debug(`Marking channel: ${channel.id} as closed.`, logger.tags.ln);
|
||||
await DB.query(`UPDATE channels SET status = 2, closing_date = FROM_UNIXTIME(?) WHERE id = ?`,
|
||||
[spendingTx.status.block_time, channel.id]);
|
||||
if (spendingTx.txid && !channel.closing_transaction_id) {
|
||||
await DB.query(`UPDATE channels SET closing_transaction_id = ? WHERE id = ?`, [spendingTx.txid, channel.id]);
|
||||
const allChannels = await channelsApi.$getChannelsByStatus([0, 1]);
|
||||
|
||||
const sliceLength = 5000;
|
||||
// process batches of 5000 channels
|
||||
for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) {
|
||||
const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength);
|
||||
const outspends = await bitcoinApi.$getOutSpendsByOutpoint(channels.map(channel => {
|
||||
return { txid: channel.transaction_id, vout: channel.transaction_vout };
|
||||
}));
|
||||
|
||||
for (const [index, channel] of channels.entries()) {
|
||||
const spendingTx = outspends[index];
|
||||
if (spendingTx.spent === true && spendingTx.status?.confirmed === true) {
|
||||
// logger.debug(`Marking channel: ${channel.id} as closed.`, logger.tags.ln);
|
||||
await DB.query(`UPDATE channels SET status = 2, closing_date = FROM_UNIXTIME(?) WHERE id = ?`,
|
||||
[spendingTx.status.block_time, channel.id]);
|
||||
if (spendingTx.txid && !channel.closing_transaction_id) {
|
||||
await DB.query(`UPDATE channels SET closing_transaction_id = ? WHERE id = ?`, [spendingTx.txid, channel.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++progress;
|
||||
progress += channels.length;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Checking if channel has been closed ${progress}/${channels.length}`, logger.tags.ln);
|
||||
logger.debug(`Checking if channel has been closed ${progress}/${allChannels.length}`, logger.tags.ln);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
|
3
contributors/TKone7.txt
Normal file
3
contributors/TKone7.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of October 23, 2023.
|
||||
|
||||
Signed: TKone7
|
3
contributors/fanquake.txt
Normal file
3
contributors/fanquake.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of October 23, 2023.
|
||||
|
||||
Signed: fanquake
|
|
@ -164,7 +164,9 @@ Corresponding `docker-compose.yml` overrides:
|
|||
"PORT": 8332,
|
||||
"USERNAME": "mempool",
|
||||
"PASSWORD": "mempool",
|
||||
"TIMEOUT": 60000
|
||||
"TIMEOUT": 60000,
|
||||
"COOKIE": false,
|
||||
"COOKIE_PATH": ""
|
||||
},
|
||||
```
|
||||
|
||||
|
@ -177,6 +179,8 @@ Corresponding `docker-compose.yml` overrides:
|
|||
CORE_RPC_USERNAME: ""
|
||||
CORE_RPC_PASSWORD: ""
|
||||
CORE_RPC_TIMEOUT: 60000
|
||||
CORE_RPC_COOKIE: false
|
||||
CORE_RPC_COOKIE_PATH: ""
|
||||
...
|
||||
```
|
||||
|
||||
|
@ -231,7 +235,9 @@ Corresponding `docker-compose.yml` overrides:
|
|||
"PORT": 8332,
|
||||
"USERNAME": "mempool",
|
||||
"PASSWORD": "mempool",
|
||||
"TIMEOUT": 60000
|
||||
"TIMEOUT": 60000,
|
||||
"COOKIE": false,
|
||||
"COOKIE_PATH": ""
|
||||
},
|
||||
```
|
||||
|
||||
|
@ -244,6 +250,8 @@ Corresponding `docker-compose.yml` overrides:
|
|||
SECOND_CORE_RPC_USERNAME: ""
|
||||
SECOND_CORE_RPC_PASSWORD: ""
|
||||
SECOND_CORE_RPC_TIMEOUT: ""
|
||||
SECOND_CORE_RPC_COOKIE: false
|
||||
SECOND_CORE_RPC_COOKIE_PATH: ""
|
||||
...
|
||||
```
|
||||
|
||||
|
|
|
@ -41,7 +41,9 @@
|
|||
"PORT": __CORE_RPC_PORT__,
|
||||
"USERNAME": "__CORE_RPC_USERNAME__",
|
||||
"PASSWORD": "__CORE_RPC_PASSWORD__",
|
||||
"TIMEOUT": __CORE_RPC_TIMEOUT__
|
||||
"TIMEOUT": __CORE_RPC_TIMEOUT__,
|
||||
"COOKIE": __CORE_RPC_COOKIE__,
|
||||
"COOKIE_PATH": "__CORE_RPC_COOKIE_PATH__"
|
||||
},
|
||||
"ELECTRUM": {
|
||||
"HOST": "__ELECTRUM_HOST__",
|
||||
|
@ -52,6 +54,8 @@
|
|||
"REST_API_URL": "__ESPLORA_REST_API_URL__",
|
||||
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
|
||||
"RETRY_UNIX_SOCKET_AFTER": __ESPLORA_RETRY_UNIX_SOCKET_AFTER__,
|
||||
"REQUEST_TIMEOUT": __ESPLORA_REQUEST_TIMEOUT__,
|
||||
"FALLBACK_TIMEOUT": __ESPLORA_FALLBACK_TIMEOUT__,
|
||||
"FALLBACK": __ESPLORA_FALLBACK__
|
||||
},
|
||||
"SECOND_CORE_RPC": {
|
||||
|
@ -59,7 +63,9 @@
|
|||
"PORT": __SECOND_CORE_RPC_PORT__,
|
||||
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
|
||||
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
|
||||
"TIMEOUT": __SECOND_CORE_RPC_TIMEOUT__
|
||||
"TIMEOUT": __SECOND_CORE_RPC_TIMEOUT__,
|
||||
"COOKIE": __SECOND_CORE_RPC_COOKIE__,
|
||||
"COOKIE_PATH": "__SECOND_CORE_RPC_COOKIE_PATH__"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": __DATABASE_ENABLED__,
|
||||
|
@ -70,7 +76,7 @@
|
|||
"USERNAME": "__DATABASE_USERNAME__",
|
||||
"PASSWORD": "__DATABASE_PASSWORD__",
|
||||
"TIMEOUT": __DATABASE_TIMEOUT__,
|
||||
"PID_DIR": "__DATABASE_PID_DIR__",
|
||||
"PID_DIR": "__DATABASE_PID_DIR__"
|
||||
},
|
||||
"SYSLOG": {
|
||||
"ENABLED": __SYSLOG_ENABLED__,
|
||||
|
|
|
@ -43,6 +43,8 @@ __CORE_RPC_PORT__=${CORE_RPC_PORT:=8332}
|
|||
__CORE_RPC_USERNAME__=${CORE_RPC_USERNAME:=mempool}
|
||||
__CORE_RPC_PASSWORD__=${CORE_RPC_PASSWORD:=mempool}
|
||||
__CORE_RPC_TIMEOUT__=${CORE_RPC_TIMEOUT:=60000}
|
||||
__CORE_RPC_COOKIE__=${CORE_RPC_COOKIE:=false}
|
||||
__CORE_RPC_COOKIE_PATH__=${CORE_RPC_COOKIE_PATH:=""}
|
||||
|
||||
# ELECTRUM
|
||||
__ELECTRUM_HOST__=${ELECTRUM_HOST:=127.0.0.1}
|
||||
|
@ -53,6 +55,8 @@ __ELECTRUM_TLS_ENABLED__=${ELECTRUM_TLS_ENABLED:=false}
|
|||
__ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000}
|
||||
__ESPLORA_UNIX_SOCKET_PATH__=${ESPLORA_UNIX_SOCKET_PATH:="null"}
|
||||
__ESPLORA_RETRY_UNIX_SOCKET_AFTER__=${ESPLORA_RETRY_UNIX_SOCKET_AFTER:=30000}
|
||||
__ESPLORA_REQUEST_TIMEOUT__=${ESPLORA_REQUEST_TIMEOUT:=5000}
|
||||
__ESPLORA_FALLBACK_TIMEOUT__=${ESPLORA_FALLBACK_TIMEOUT:=5000}
|
||||
__ESPLORA_FALLBACK__=${ESPLORA_FALLBACK:=[]}
|
||||
|
||||
# SECOND_CORE_RPC
|
||||
|
@ -61,6 +65,8 @@ __SECOND_CORE_RPC_PORT__=${SECOND_CORE_RPC_PORT:=8332}
|
|||
__SECOND_CORE_RPC_USERNAME__=${SECOND_CORE_RPC_USERNAME:=mempool}
|
||||
__SECOND_CORE_RPC_PASSWORD__=${SECOND_CORE_RPC_PASSWORD:=mempool}
|
||||
__SECOND_CORE_RPC_TIMEOUT__=${SECOND_CORE_RPC_TIMEOUT:=60000}
|
||||
__SECOND_CORE_RPC_COOKIE__=${SECOND_CORE_RPC_COOKIE:=false}
|
||||
__SECOND_CORE_RPC_COOKIE_PATH__=${SECOND_CORE_RPC_COOKIE_PATH:=""}
|
||||
|
||||
# DATABASE
|
||||
__DATABASE_ENABLED__=${DATABASE_ENABLED:=true}
|
||||
|
@ -186,6 +192,8 @@ sed -i "s!__CORE_RPC_PORT__!${__CORE_RPC_PORT__}!g" mempool-config.json
|
|||
sed -i "s!__CORE_RPC_USERNAME__!${__CORE_RPC_USERNAME__}!g" mempool-config.json
|
||||
sed -i "s!__CORE_RPC_PASSWORD__!${__CORE_RPC_PASSWORD__}!g" mempool-config.json
|
||||
sed -i "s!__CORE_RPC_TIMEOUT__!${__CORE_RPC_TIMEOUT__}!g" mempool-config.json
|
||||
sed -i "s!__CORE_RPC_COOKIE__!${__CORE_RPC_COOKIE__}!g" mempool-config.json
|
||||
sed -i "s!__CORE_RPC_COOKIE_PATH__!${__CORE_RPC_COOKIE_PATH__}!g" mempool-config.json
|
||||
|
||||
sed -i "s!__ELECTRUM_HOST__!${__ELECTRUM_HOST__}!g" mempool-config.json
|
||||
sed -i "s!__ELECTRUM_PORT__!${__ELECTRUM_PORT__}!g" mempool-config.json
|
||||
|
@ -194,6 +202,8 @@ sed -i "s!__ELECTRUM_TLS_ENABLED__!${__ELECTRUM_TLS_ENABLED__}!g" mempool-config
|
|||
sed -i "s!__ESPLORA_REST_API_URL__!${__ESPLORA_REST_API_URL__}!g" mempool-config.json
|
||||
sed -i "s!__ESPLORA_UNIX_SOCKET_PATH__!${__ESPLORA_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
||||
sed -i "s!__ESPLORA_RETRY_UNIX_SOCKET_AFTER__!${__ESPLORA_RETRY_UNIX_SOCKET_AFTER__}!g" mempool-config.json
|
||||
sed -i "s!__ESPLORA_REQUEST_TIMEOUT__!${__ESPLORA_REQUEST_TIMEOUT__}!g" mempool-config.json
|
||||
sed -i "s!__ESPLORA_FALLBACK_TIMEOUT__!${__ESPLORA_FALLBACK_TIMEOUT__}!g" mempool-config.json
|
||||
sed -i "s!__ESPLORA_FALLBACK__!${__ESPLORA_FALLBACK__}!g" mempool-config.json
|
||||
|
||||
sed -i "s!__SECOND_CORE_RPC_HOST__!${__SECOND_CORE_RPC_HOST__}!g" mempool-config.json
|
||||
|
@ -201,6 +211,8 @@ sed -i "s!__SECOND_CORE_RPC_PORT__!${__SECOND_CORE_RPC_PORT__}!g" mempool-config
|
|||
sed -i "s!__SECOND_CORE_RPC_USERNAME__!${__SECOND_CORE_RPC_USERNAME__}!g" mempool-config.json
|
||||
sed -i "s!__SECOND_CORE_RPC_PASSWORD__!${__SECOND_CORE_RPC_PASSWORD__}!g" mempool-config.json
|
||||
sed -i "s!__SECOND_CORE_RPC_TIMEOUT__!${__SECOND_CORE_RPC_TIMEOUT__}!g" mempool-config.json
|
||||
sed -i "s!__SECOND_CORE_RPC_COOKIE__!${__SECOND_CORE_RPC_COOKIE__}!g" mempool-config.json
|
||||
sed -i "s!__SECOND_CORE_RPC_COOKIE_PATH__!${__SECOND_CORE_RPC_COOKIE_PATH__}!g" mempool-config.json
|
||||
|
||||
sed -i "s!__DATABASE_ENABLED__!${__DATABASE_ENABLED__}!g" mempool-config.json
|
||||
sed -i "s!__DATABASE_HOST__!${__DATABASE_HOST__}!g" mempool-config.json
|
||||
|
|
367
frontend/package-lock.json
generated
367
frontend/package-lock.json
generated
|
@ -31,12 +31,11 @@
|
|||
"bootstrap": "~4.6.2",
|
||||
"browserify": "^17.0.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"cypress": "^13.3.0",
|
||||
"domino": "^2.1.6",
|
||||
"echarts": "~5.4.3",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"lightweight-charts": "~3.8.0",
|
||||
"ngx-echarts": "~16.0.0",
|
||||
"mock-socket": "~9.3.1",
|
||||
"ngx-echarts": "~16.2.0",
|
||||
"ngx-infinite-scroll": "^16.0.0",
|
||||
"qrcode": "1.5.1",
|
||||
"rxjs": "~7.8.1",
|
||||
|
@ -60,10 +59,10 @@
|
|||
"optionalDependencies": {
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^13.3.0",
|
||||
"cypress": "^13.5.0",
|
||||
"cypress-fail-on-console-error": "~5.0.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"mock-socket": "~9.2.1",
|
||||
"mock-socket": "~9.3.1",
|
||||
"start-server-and-test": "~2.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -1251,11 +1250,12 @@
|
|||
"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg=="
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
||||
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.22.5"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
@ -1465,20 +1465,33 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
|
||||
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
|
||||
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name/node_modules/@babel/template": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
@ -1628,9 +1641,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
|
@ -1670,12 +1683,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
|
||||
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1683,9 +1696,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
|
||||
"integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
|
@ -2880,18 +2893,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.22.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
|
||||
"integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/generator": "^7.22.7",
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-function-name": "^7.22.5",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.22.7",
|
||||
"@babel/types": "^7.22.5",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
|
@ -2899,13 +2912,36 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/@babel/generator": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
|
||||
"integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -5575,9 +5611,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
|
||||
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
|
@ -5872,25 +5908,28 @@
|
|||
}
|
||||
},
|
||||
"node_modules/browserify-sign": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
|
||||
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
|
||||
"integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
|
||||
"dependencies": {
|
||||
"bn.js": "^5.1.1",
|
||||
"browserify-rsa": "^4.0.1",
|
||||
"bn.js": "^5.2.1",
|
||||
"browserify-rsa": "^4.1.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.5.3",
|
||||
"elliptic": "^6.5.4",
|
||||
"inherits": "^2.0.4",
|
||||
"parse-asn1": "^5.1.5",
|
||||
"readable-stream": "^3.6.0",
|
||||
"safe-buffer": "^5.2.0"
|
||||
"parse-asn1": "^5.1.6",
|
||||
"readable-stream": "^3.6.2",
|
||||
"safe-buffer": "^5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/browserify-sign/node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
@ -6390,11 +6429,6 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/claygl": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/claygl/-/claygl-1.3.0.tgz",
|
||||
"integrity": "sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ=="
|
||||
},
|
||||
"node_modules/clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
|
@ -7114,9 +7148,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz",
|
||||
"integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==",
|
||||
"version": "13.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.0.tgz",
|
||||
"integrity": "sha512-oh6U7h9w8wwHfzNDJQ6wVcAeXu31DlIYlNOBvfd6U4CcB8oe4akawQmH+QJVOMZlM42eBoCne015+svVqdwdRQ==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
@ -7786,18 +7820,6 @@
|
|||
"zrender": "5.4.4"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts-gl": {
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/echarts-gl/-/echarts-gl-2.0.9.tgz",
|
||||
"integrity": "sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==",
|
||||
"dependencies": {
|
||||
"claygl": "^1.2.1",
|
||||
"zrender": "^5.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"echarts": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
|
@ -9492,9 +9514,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
|
||||
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
|
@ -12170,9 +12192,9 @@
|
|||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"node_modules/mock-socket": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.2.1.tgz",
|
||||
"integrity": "sha512-aw9F9T9G2zpGipLLhSNh6ZpgUyUl4frcVmRN08uE1NWPWg43Wx6+sGPDbQ7E5iFZZDJW5b5bypMeAEHqTbIFag==",
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz",
|
||||
"integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
|
@ -12421,9 +12443,9 @@
|
|||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"node_modules/ngx-echarts": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-16.0.0.tgz",
|
||||
"integrity": "sha512-hdM7/CL29bY3sF3V5ihb7H1NeUsQlhijp8tVxT23+vkNTf9SJrUHPjs9oHOMkbTlr2Q8HB+eVpckYAL/tuK0CQ==",
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-16.2.0.tgz",
|
||||
"integrity": "sha512-yhuDbp6qdkmR4kRVLS06Z0Iumod7xOj5n/Z++clRiKM24OQ4sM8WuOTicdfWy6eeYDNywdGSrri4Y5SUGRD8bg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
|
@ -17769,11 +17791,12 @@
|
|||
"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg=="
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
||||
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.22.5"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
|
@ -17938,17 +17961,29 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
|
||||
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q=="
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA=="
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
|
||||
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/template": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
|
@ -18050,9 +18085,9 @@
|
|||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ=="
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.22.5",
|
||||
|
@ -18080,19 +18115,19 @@
|
|||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
|
||||
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
|
||||
"integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q=="
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw=="
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
"version": "7.22.5",
|
||||
|
@ -18869,29 +18904,51 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.22.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
|
||||
"integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/generator": "^7.22.7",
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-function-name": "^7.22.5",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.22.7",
|
||||
"@babel/types": "^7.22.5",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
|
||||
"integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
|
@ -20847,9 +20904,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
|
||||
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.20.1",
|
||||
|
@ -21193,25 +21250,25 @@
|
|||
}
|
||||
},
|
||||
"browserify-sign": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
|
||||
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
|
||||
"integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
|
||||
"requires": {
|
||||
"bn.js": "^5.1.1",
|
||||
"browserify-rsa": "^4.0.1",
|
||||
"bn.js": "^5.2.1",
|
||||
"browserify-rsa": "^4.1.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"elliptic": "^6.5.3",
|
||||
"elliptic": "^6.5.4",
|
||||
"inherits": "^2.0.4",
|
||||
"parse-asn1": "^5.1.5",
|
||||
"readable-stream": "^3.6.0",
|
||||
"safe-buffer": "^5.2.0"
|
||||
"parse-asn1": "^5.1.6",
|
||||
"readable-stream": "^3.6.2",
|
||||
"safe-buffer": "^5.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
@ -21476,11 +21533,6 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"claygl": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/claygl/-/claygl-1.3.0.tgz",
|
||||
"integrity": "sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ=="
|
||||
},
|
||||
"clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
|
@ -22044,9 +22096,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"cypress": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz",
|
||||
"integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==",
|
||||
"version": "13.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.0.tgz",
|
||||
"integrity": "sha512-oh6U7h9w8wwHfzNDJQ6wVcAeXu31DlIYlNOBvfd6U4CcB8oe4akawQmH+QJVOMZlM42eBoCne015+svVqdwdRQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@cypress/request": "^3.0.0",
|
||||
|
@ -22585,15 +22637,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"echarts-gl": {
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/echarts-gl/-/echarts-gl-2.0.9.tgz",
|
||||
"integrity": "sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==",
|
||||
"requires": {
|
||||
"claygl": "^1.2.1",
|
||||
"zrender": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
@ -23897,9 +23940,9 @@
|
|||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||
},
|
||||
"get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
|
||||
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
|
||||
"optional": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
|
@ -25871,9 +25914,9 @@
|
|||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"mock-socket": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.2.1.tgz",
|
||||
"integrity": "sha512-aw9F9T9G2zpGipLLhSNh6ZpgUyUl4frcVmRN08uE1NWPWg43Wx6+sGPDbQ7E5iFZZDJW5b5bypMeAEHqTbIFag==",
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz",
|
||||
"integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==",
|
||||
"optional": true
|
||||
},
|
||||
"module-deps": {
|
||||
|
@ -26067,9 +26110,9 @@
|
|||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"ngx-echarts": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-16.0.0.tgz",
|
||||
"integrity": "sha512-hdM7/CL29bY3sF3V5ihb7H1NeUsQlhijp8tVxT23+vkNTf9SJrUHPjs9oHOMkbTlr2Q8HB+eVpckYAL/tuK0CQ==",
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-16.2.0.tgz",
|
||||
"integrity": "sha512-yhuDbp6qdkmR4kRVLS06Z0Iumod7xOj5n/Z++clRiKM24OQ4sM8WuOTicdfWy6eeYDNywdGSrri4Y5SUGRD8bg==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
|
|
|
@ -85,9 +85,8 @@
|
|||
"clipboard": "^2.0.11",
|
||||
"domino": "^2.1.6",
|
||||
"echarts": "~5.4.3",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"lightweight-charts": "~3.8.0",
|
||||
"ngx-echarts": "~16.0.0",
|
||||
"ngx-echarts": "~16.2.0",
|
||||
"ngx-infinite-scroll": "^16.0.0",
|
||||
"qrcode": "1.5.1",
|
||||
"rxjs": "~7.8.1",
|
||||
|
@ -111,10 +110,10 @@
|
|||
"optionalDependencies": {
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^13.3.0",
|
||||
"cypress": "^13.5.0",
|
||||
"cypress-fail-on-console-error": "~5.0.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"mock-socket": "~9.2.1",
|
||||
"mock-socket": "~9.3.1",
|
||||
"start-server-and-test": "~2.0.0"
|
||||
},
|
||||
"scarfSettings": {
|
||||
|
|
|
@ -1,28 +1,10 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { AppPreloadingStrategy } from './app.preloading-strategy'
|
||||
import { StartComponent } from './components/start/start.component';
|
||||
import { TransactionComponent } from './components/transaction/transaction.component';
|
||||
import { BlockComponent } from './components/block/block.component';
|
||||
import { BlockViewComponent } from './components/block-view/block-view.component';
|
||||
import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component';
|
||||
import { ClockComponent } from './components/clock/clock.component';
|
||||
import { AddressComponent } from './components/address/address.component';
|
||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||
import { AboutComponent } from './components/about/about.component';
|
||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
||||
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
|
||||
import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component';
|
||||
import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component';
|
||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
|
||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||
import { RbfList } from './components/rbf-list/rbf-list.component';
|
||||
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
|
||||
import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
|
||||
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
|
||||
import { AssetsComponent } from './components/assets/assets.component';
|
||||
import { AssetComponent } from './components/asset/asset.component';
|
||||
import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component';
|
||||
import { CalculatorComponent } from './components/calculator/calculator.component';
|
||||
|
||||
const browserWindow = window || {};
|
||||
// @ts-ignore
|
||||
|
@ -35,95 +17,13 @@ let routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule),
|
||||
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'mining/blocks',
|
||||
redirectTo: 'blocks',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'rbf',
|
||||
component: RbfList,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
component: PrivacyPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
component: TrademarkPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent,
|
||||
data: {
|
||||
ogImage: true,
|
||||
networkSpecific: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tx',
|
||||
component: StartComponent,
|
||||
data: { networkSpecific: true },
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'block',
|
||||
component: StartComponent,
|
||||
data: { networkSpecific: true },
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: BlockComponent,
|
||||
data: {
|
||||
ogImage: true
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'lightning',
|
||||
loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule),
|
||||
data: { preload: browserWindowEnv && browserWindowEnv.LIGHTNING === true, networks: ['bitcoin'] },
|
||||
},
|
||||
],
|
||||
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
|
@ -132,7 +32,8 @@ let routes: Routes = [
|
|||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
|
@ -151,88 +52,13 @@ let routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'rbf',
|
||||
component: RbfList,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
component: PrivacyPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
component: TrademarkPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent,
|
||||
data: {
|
||||
ogImage: true,
|
||||
networkSpecific: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tx',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'block',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: BlockComponent,
|
||||
data: {
|
||||
ogImage: true
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'lightning',
|
||||
data: { networks: ['bitcoin'] },
|
||||
loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule)
|
||||
},
|
||||
],
|
||||
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
|
@ -241,7 +67,8 @@ let routes: Routes = [
|
|||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
|
@ -252,97 +79,13 @@ let routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'mining/blocks',
|
||||
redirectTo: 'blocks',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'rbf',
|
||||
component: RbfList,
|
||||
},
|
||||
{
|
||||
path: 'tools/calculator',
|
||||
component: CalculatorComponent
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
component: PrivacyPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
component: TrademarkPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent,
|
||||
data: {
|
||||
ogImage: true,
|
||||
networkSpecific: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tx',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'block',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: BlockComponent,
|
||||
data: {
|
||||
ogImage: true
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'lightning',
|
||||
data: { networks: ['bitcoin'] },
|
||||
loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule)
|
||||
},
|
||||
],
|
||||
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'preview',
|
||||
|
@ -373,6 +116,14 @@ let routes: Routes = [
|
|||
path: 'clock/:mode/:index',
|
||||
component: ClockComponent,
|
||||
},
|
||||
{
|
||||
path: 'view/block/:id',
|
||||
component: BlockViewComponent,
|
||||
},
|
||||
{
|
||||
path: 'view/mempool-block/:index',
|
||||
component: MempoolBlockViewComponent,
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
|
@ -380,7 +131,8 @@ let routes: Routes = [
|
|||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
|
@ -391,7 +143,6 @@ let routes: Routes = [
|
|||
if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'bisq') {
|
||||
routes = [{
|
||||
path: '',
|
||||
component: BisqMasterPageComponent,
|
||||
loadChildren: () => import('./bisq/bisq.module').then(m => m.BisqModule)
|
||||
}];
|
||||
}
|
||||
|
@ -404,105 +155,13 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: LiquidMasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
component: PrivacyPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
component: TrademarkPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent,
|
||||
data: {
|
||||
ogImage: true,
|
||||
networkSpecific: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tx',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'block',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: BlockComponent,
|
||||
data: {
|
||||
ogImage: true
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
data: { networks: ['liquid'] },
|
||||
component: AssetsNavComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'all',
|
||||
data: { networks: ['liquid'] },
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'group/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: AssetGroupComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'all'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
],
|
||||
loadChildren: () => import ('./liquid/liquid-master-page.module').then(m => m.LiquidMasterPageModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
|
@ -511,7 +170,8 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
|
@ -522,110 +182,13 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: LiquidMasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
component: PrivacyPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
component: TrademarkPolicyComponent
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent,
|
||||
data: {
|
||||
ogImage: true,
|
||||
networkSpecific: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tx',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'block',
|
||||
data: { networkSpecific: true },
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: BlockComponent,
|
||||
data: {
|
||||
ogImage: true
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
data: { networks: ['liquid'] },
|
||||
component: AssetsNavComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'featured',
|
||||
data: { networkSpecific: true },
|
||||
component: AssetsFeaturedComponent,
|
||||
},
|
||||
{
|
||||
path: 'all',
|
||||
data: { networks: ['liquid'] },
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'group/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: AssetGroupComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'featured'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
],
|
||||
loadChildren: () => import ('./liquid/liquid-master-page.module').then(m => m.LiquidMasterPageModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'preview',
|
||||
|
@ -647,7 +210,8 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule)
|
||||
loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
|
|
|
@ -27,9 +27,11 @@ import { AutofocusDirective } from '../components/ngx-bootstrap-multiselect/auto
|
|||
import { MultiSelectSearchFilter } from '../components/ngx-bootstrap-multiselect/search-filter.pipe';
|
||||
import { OffClickDirective } from '../components/ngx-bootstrap-multiselect/off-click.directive';
|
||||
import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-multiselect/ngx-bootstrap-multiselect.component';
|
||||
import { BisqMasterPageComponent } from '../components/bisq-master-page/bisq-master-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
BisqMasterPageComponent,
|
||||
BisqTransactionsComponent,
|
||||
BisqTransactionComponent,
|
||||
BisqBlockComponent,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AboutComponent } from '../components/about/about.component';
|
||||
import { BisqMasterPageComponent } from '../components/bisq-master-page/bisq-master-page.component';
|
||||
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
||||
import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component';
|
||||
import { BisqBlockComponent } from './bisq-block/bisq-block.component';
|
||||
|
@ -10,78 +10,83 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
|||
import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.component';
|
||||
import { BisqMarketComponent } from './bisq-market/bisq-market.component';
|
||||
import { BisqMainDashboardComponent } from './bisq-main-dashboard/bisq-main-dashboard.component';
|
||||
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
|
||||
import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: BisqMainDashboardComponent,
|
||||
},
|
||||
{
|
||||
path: 'markets',
|
||||
data: { networks: ['bisq'] },
|
||||
component: BisqDashboardComponent,
|
||||
},
|
||||
{
|
||||
path: 'transactions',
|
||||
data: { networks: ['bisq'] },
|
||||
component: BisqTransactionsComponent
|
||||
},
|
||||
{
|
||||
path: 'market/:pair',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqMarketComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqTransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
children: [],
|
||||
component: BisqBlocksComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqBlockComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqAddressComponent,
|
||||
},
|
||||
{
|
||||
path: 'stats',
|
||||
data: { networks: ['bisq'] },
|
||||
component: BisqStatsComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('../docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('../docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
component: TermsOfServiceComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
}
|
||||
{
|
||||
path: '',
|
||||
component: BisqMasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: BisqMainDashboardComponent,
|
||||
},
|
||||
{
|
||||
path: 'markets',
|
||||
data: { networks: ['bisq'] },
|
||||
component: BisqDashboardComponent,
|
||||
},
|
||||
{
|
||||
path: 'transactions',
|
||||
data: { networks: ['bisq'] },
|
||||
component: BisqTransactionsComponent
|
||||
},
|
||||
{
|
||||
path: 'market/:pair',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqMarketComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqTransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
children: [],
|
||||
component: BisqBlocksComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqBlockComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: BisqAddressComponent,
|
||||
},
|
||||
{
|
||||
path: 'stats',
|
||||
data: { networks: ['bisq'] },
|
||||
component: BisqStatsComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
loadChildren: () => import('../components/about/about.module').then(m => m.AboutModule),
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('../docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('../docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
loadChildren: () => import('../components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule),
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
37
frontend/src/app/bitcoin-graphs.module.ts
Normal file
37
frontend/src/app/bitcoin-graphs.module.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule),
|
||||
data: { preload: true },
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class BitcoinGraphsRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
BitcoinGraphsRoutingModule,
|
||||
],
|
||||
})
|
||||
export class BitcoinGraphsModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
40
frontend/src/app/components/about/about.module.ts
Normal file
40
frontend/src/app/components/about/about.module.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { AboutComponent } from './about.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AboutComponent,
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class AboutRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
AboutRoutingModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
AboutComponent,
|
||||
]
|
||||
})
|
||||
export class AboutModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable, Subscription, combineLatest } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
@ -123,11 +123,11 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||
this.chartOptions = {
|
||||
title: title,
|
||||
color: [
|
||||
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#FDD835' },
|
||||
{ offset: 1, color: '#FB8C00' },
|
||||
]),
|
||||
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#C0CA33' },
|
||||
{ offset: 1, color: '#1B5E20' },
|
||||
]),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
@ -123,11 +123,11 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||
title: title,
|
||||
animation: false,
|
||||
color: [
|
||||
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#FDD835' },
|
||||
{ offset: 1, color: '#FB8C00' },
|
||||
]),
|
||||
new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#C0CA33' },
|
||||
{ offset: 1, color: '#1B5E20' },
|
||||
]),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
|
||||
import { EChartsOption} from 'echarts';
|
||||
import { EChartsOption} from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<div class="block-wrapper">
|
||||
<div class="block-container">
|
||||
<app-block-overview-graph
|
||||
#blockGraph
|
||||
[isLoading]="false"
|
||||
[resolution]="resolution"
|
||||
[blockLimit]="stateService.blockVSize"
|
||||
[orientation]="'top'"
|
||||
[flip]="false"
|
||||
[disableSpinner]="true"
|
||||
(txClickEvent)="onTxClick($event)"
|
||||
></app-block-overview-graph>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,22 @@
|
|||
.block-wrapper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #181b2d;
|
||||
}
|
||||
|
||||
.block-container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 100vw;
|
||||
max-width: 100vh;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
* {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
180
frontend/src/app/components/block-view/block-view.component.ts
Normal file
180
frontend/src/app/components/block-view/block-view.component.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import { Component, OnInit, OnDestroy, ViewChild, HostListener } from '@angular/core';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { switchMap, tap, catchError, shareReplay, filter } from 'rxjs/operators';
|
||||
import { of, Subscription } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||
import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
|
||||
function bestFitResolution(min, max, n): number {
|
||||
const target = (min + max) / 2;
|
||||
let bestScore = Infinity;
|
||||
let best = null;
|
||||
for (let i = min; i <= max; i++) {
|
||||
const remainder = (n % i);
|
||||
if (remainder < bestScore || (remainder === bestScore && (Math.abs(i - target) < Math.abs(best - target)))) {
|
||||
bestScore = remainder;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-view',
|
||||
templateUrl: './block-view.component.html',
|
||||
styleUrls: ['./block-view.component.scss']
|
||||
})
|
||||
export class BlockViewComponent implements OnInit, OnDestroy {
|
||||
network = '';
|
||||
block: BlockExtended;
|
||||
blockHeight: number;
|
||||
blockHash: string;
|
||||
rawId: string;
|
||||
isLoadingBlock = true;
|
||||
strippedTransactions: TransactionStripped[];
|
||||
isLoadingOverview = true;
|
||||
autofit: boolean = false;
|
||||
resolution: number = 80;
|
||||
|
||||
overviewSubscription: Subscription;
|
||||
networkChangedSubscription: Subscription;
|
||||
queryParamsSubscription: Subscription;
|
||||
|
||||
@ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
public stateService: StateService,
|
||||
private seoService: SeoService,
|
||||
private apiService: ApiService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.network = this.stateService.network;
|
||||
|
||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||
this.autofit = params.autofit === 'true';
|
||||
if (this.autofit) {
|
||||
this.onResize();
|
||||
}
|
||||
});
|
||||
|
||||
const block$ = this.route.paramMap.pipe(
|
||||
switchMap((params: ParamMap) => {
|
||||
this.rawId = params.get('id') || '';
|
||||
|
||||
const blockHash: string = params.get('id') || '';
|
||||
this.block = undefined;
|
||||
|
||||
let isBlockHeight = false;
|
||||
if (/^[0-9]+$/.test(blockHash)) {
|
||||
isBlockHeight = true;
|
||||
} else {
|
||||
this.blockHash = blockHash;
|
||||
}
|
||||
|
||||
this.isLoadingBlock = true;
|
||||
this.isLoadingOverview = true;
|
||||
|
||||
if (isBlockHeight) {
|
||||
return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockHash, 10))
|
||||
.pipe(
|
||||
switchMap((hash) => {
|
||||
if (hash) {
|
||||
this.blockHash = hash;
|
||||
return this.apiService.getBlock$(hash);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
catchError(() => {
|
||||
return of(null);
|
||||
}),
|
||||
);
|
||||
}
|
||||
return this.apiService.getBlock$(blockHash);
|
||||
}),
|
||||
filter((block: BlockExtended | void) => block != null),
|
||||
tap((block: BlockExtended) => {
|
||||
this.block = block;
|
||||
this.blockHeight = block.height;
|
||||
|
||||
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
||||
if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) {
|
||||
this.seoService.setDescription($localize`:@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||
} else {
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`);
|
||||
}
|
||||
this.isLoadingBlock = false;
|
||||
this.isLoadingOverview = true;
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
this.overviewSubscription = block$.pipe(
|
||||
switchMap((block) => this.apiService.getStrippedBlockTransactions$(block.id)
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
return of([]);
|
||||
}),
|
||||
switchMap((transactions) => {
|
||||
return of(transactions);
|
||||
})
|
||||
)
|
||||
),
|
||||
)
|
||||
.subscribe((transactions: TransactionStripped[]) => {
|
||||
this.strippedTransactions = transactions;
|
||||
this.isLoadingOverview = false;
|
||||
if (this.blockGraph) {
|
||||
this.blockGraph.destroy();
|
||||
this.blockGraph.setup(this.strippedTransactions);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.isLoadingOverview = false;
|
||||
if (this.blockGraph) {
|
||||
this.blockGraph.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
this.networkChangedSubscription = this.stateService.networkChanged$
|
||||
.subscribe((network) => this.network = network);
|
||||
}
|
||||
|
||||
onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
|
||||
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
|
||||
if (!event.keyModifier) {
|
||||
this.router.navigate([url]);
|
||||
} else {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
if (this.autofit) {
|
||||
this.resolution = bestFitResolution(64, 96, Math.min(window.innerWidth, window.innerHeight));
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.overviewSubscription) {
|
||||
this.overviewSubscription.unsubscribe();
|
||||
}
|
||||
if (this.networkChangedSubscription) {
|
||||
this.networkChangedSubscription.unsubscribe();
|
||||
}
|
||||
if (this.queryParamsSubscription) {
|
||||
this.queryParamsSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
43
frontend/src/app/components/block/block.module.ts
Normal file
43
frontend/src/app/components/block/block.module.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { BlockComponent } from './block.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':id',
|
||||
component: BlockComponent,
|
||||
data: {
|
||||
ogImage: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class BlockRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
BlockRoutingModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
BlockComponent,
|
||||
]
|
||||
})
|
||||
export class BlockModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.fee-distribution-chart {
|
||||
margin-top: 0.75rem;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { OnChanges, OnDestroy } from '@angular/core';
|
||||
import { HostListener, OnChanges, OnDestroy } from '@angular/core';
|
||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
@ -9,6 +9,7 @@ import { Subscription } from 'rxjs';
|
|||
@Component({
|
||||
selector: 'app-fee-distribution-graph',
|
||||
templateUrl: './fee-distribution-graph.component.html',
|
||||
styleUrls: ['./fee-distribution-graph.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
@ -25,6 +26,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
|
|||
simple: boolean = false;
|
||||
data: number[][];
|
||||
labelInterval: number = 50;
|
||||
smallScreen: boolean = window.innerWidth < 450;
|
||||
|
||||
rateUnitSub: Subscription;
|
||||
weightMode: boolean = false;
|
||||
|
@ -95,9 +97,9 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
|
|||
this.mempoolVsizeFeesOptions = {
|
||||
grid: {
|
||||
height: '210',
|
||||
right: '20',
|
||||
right: this.smallScreen ? '10' : '20',
|
||||
top: '22',
|
||||
left: '40',
|
||||
left: this.smallScreen ? '10' : '40',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
|
@ -131,16 +133,17 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
|
|||
}
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
show: !this.smallScreen,
|
||||
formatter: (value: number): string => {
|
||||
const unitValue = this.weightMode ? value / 4 : value;
|
||||
const selectedPowerOfTen = selectPowerOfTen(unitValue);
|
||||
const newVal = Math.round(unitValue / selectedPowerOfTen.divider);
|
||||
const scaledValue = unitValue / selectedPowerOfTen.divider;
|
||||
const newVal = scaledValue >= 100 ? Math.round(scaledValue) : scaledValue.toPrecision(3);
|
||||
return `${newVal}${selectedPowerOfTen.unit}`;
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
show: !this.smallScreen,
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
|
@ -151,11 +154,13 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
|
|||
position: 'top',
|
||||
color: '#ffffff',
|
||||
textShadowBlur: 0,
|
||||
fontSize: this.smallScreen ? 10 : 12,
|
||||
formatter: (label: { data: number[] }): string => {
|
||||
const value = label.data[1];
|
||||
const unitValue = this.weightMode ? value / 4 : value;
|
||||
const selectedPowerOfTen = selectPowerOfTen(unitValue);
|
||||
const newVal = Math.round(unitValue / selectedPowerOfTen.divider);
|
||||
const scaledValue = unitValue / selectedPowerOfTen.divider;
|
||||
const newVal = scaledValue >= 100 ? Math.round(scaledValue) : scaledValue.toPrecision(3);
|
||||
return `${newVal}${selectedPowerOfTen.unit}`;
|
||||
}
|
||||
},
|
||||
|
@ -179,6 +184,16 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr
|
|||
};
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
const isSmallScreen = window.innerWidth < 450;
|
||||
if (this.smallScreen !== isSmallScreen) {
|
||||
this.smallScreen = isSmallScreen;
|
||||
this.prepareChart();
|
||||
this.mountChart();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.rateUnitSub.unsubscribe();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||
import { merge, Observable, of } from 'rxjs';
|
||||
import { map, mergeMap, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
@ -204,7 +204,7 @@ export class HashrateChartComponent implements OnInit {
|
|||
title: title,
|
||||
animation: false,
|
||||
color: [
|
||||
new graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
{ offset: 0, color: '#F4511E99' },
|
||||
{ offset: 0.25, color: '#FB8C0099' },
|
||||
{ offset: 0.5, color: '#FFB30099' },
|
||||
|
@ -212,7 +212,7 @@ export class HashrateChartComponent implements OnInit {
|
|||
{ offset: 1, color: '#7CB34299' }
|
||||
]),
|
||||
'#D81B60',
|
||||
new graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
{ offset: 0, color: '#F4511E' },
|
||||
{ offset: 0.25, color: '#FB8C00' },
|
||||
{ offset: 0.5, color: '#FFB300' },
|
||||
|
@ -342,7 +342,7 @@ export class HashrateChartComponent implements OnInit {
|
|||
type: 'value',
|
||||
axisLabel: {
|
||||
color: 'rgb(110, 112, 121)',
|
||||
formatter: (val) => {
|
||||
formatter: (val): string => {
|
||||
const selectedPowerOfTen: any = selectPowerOfTen(val);
|
||||
const newVal = Math.round(val / selectedPowerOfTen.divider);
|
||||
return `${newVal} ${selectedPowerOfTen.unit}H/s`;
|
||||
|
@ -364,9 +364,9 @@ export class HashrateChartComponent implements OnInit {
|
|||
position: 'right',
|
||||
axisLabel: {
|
||||
color: 'rgb(110, 112, 121)',
|
||||
formatter: (val) => {
|
||||
formatter: (val): string => {
|
||||
if (this.stateService.network === 'signet') {
|
||||
return val;
|
||||
return `${val}`;
|
||||
}
|
||||
const selectedPowerOfTen: any = selectPowerOfTen(val);
|
||||
const newVal = Math.round(val / selectedPowerOfTen.divider);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { delay, map, retryWhen, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
import { OnChanges } from '@angular/core';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/graphs.utils';
|
||||
|
@ -63,7 +63,8 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||
return;
|
||||
}
|
||||
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
|
||||
this.MA = this.calculateMA(this.data.series[0]);
|
||||
const windowSize = Math.max(10, Math.floor(this.data.series[0].length / 8));
|
||||
this.MA = this.calculateMA(this.data.series[0], windowSize);
|
||||
this.mountChart();
|
||||
}
|
||||
|
||||
|
@ -74,33 +75,22 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||
this.isLoading = false;
|
||||
}
|
||||
|
||||
/// calculate the moving average of maData
|
||||
calculateMA(maData): number[][] {
|
||||
/// calculate the moving average of the provided data based on windowSize
|
||||
calculateMA(data: number[][], windowSize: number = 100): number[][] {
|
||||
//update const variables that are not changed
|
||||
const ma: number[][] = [];
|
||||
let sum = 0;
|
||||
let i = 0;
|
||||
const len = maData.length;
|
||||
|
||||
//Adjust window length based on the length of the data
|
||||
//5% appeared as a good amount from tests
|
||||
//TODO: make this a text box in the UI
|
||||
const maWindowLen = Math.ceil(len * 0.05);
|
||||
|
||||
//calculate the center of the moving average window
|
||||
const center = Math.floor(maWindowLen / 2);
|
||||
|
||||
//calculate the centered moving average
|
||||
for (i = center; i < len - center; i++) {
|
||||
sum = 0;
|
||||
//build out ma as we loop through the data
|
||||
ma[i] = [];
|
||||
ma[i].push(maData[i][0]);
|
||||
for (let j = i - center; j <= i + center; j++) {
|
||||
sum += maData[j][1];
|
||||
for (i = 0; i < data.length; i++) {
|
||||
sum += data[i][1];
|
||||
if (i >= windowSize) {
|
||||
sum -= data[i - windowSize][1];
|
||||
const midpoint = i - Math.floor(windowSize / 2);
|
||||
const avg = sum / windowSize;
|
||||
ma.push([data[midpoint][0], avg]);
|
||||
}
|
||||
|
||||
ma[i].push(sum / maWindowLen);
|
||||
}
|
||||
|
||||
//return the moving average array
|
||||
|
@ -138,36 +128,22 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||
}
|
||||
}],
|
||||
}
|
||||
},
|
||||
{
|
||||
zlevel: 0,
|
||||
name: 'MA',
|
||||
data: this.MA,
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "white",
|
||||
},
|
||||
markLine: {
|
||||
silent: true,
|
||||
});
|
||||
if (this.template !== 'widget') {
|
||||
seriesGraph.push({
|
||||
zlevel: 0,
|
||||
name: 'MA',
|
||||
data: this.MA,
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
showSymbol: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
opacity: 1,
|
||||
width: 2,
|
||||
},
|
||||
data: [{
|
||||
yAxis: 1667,
|
||||
label: {
|
||||
show: false,
|
||||
color: '#ffffff',
|
||||
}
|
||||
}],
|
||||
}
|
||||
});
|
||||
color: "white",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.mempoolStatsChartOption = {
|
||||
grid: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { formatDate, formatNumber } from '@angular/common';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lbtc-pegs-graph',
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<div class="block-wrapper">
|
||||
<div class="block-container">
|
||||
<app-mempool-block-overview [index]="index"></app-mempool-block-overview>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,22 @@
|
|||
.block-wrapper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #181b2d;
|
||||
}
|
||||
|
||||
.block-container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 100vw;
|
||||
max-width: 100vh;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
* {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { Subscription, filter, map, switchMap, tap } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
|
||||
function bestFitResolution(min, max, n): number {
|
||||
const target = (min + max) / 2;
|
||||
let bestScore = Infinity;
|
||||
let best = null;
|
||||
for (let i = min; i <= max; i++) {
|
||||
const remainder = (n % i);
|
||||
if (remainder < bestScore || (remainder === bestScore && (Math.abs(i - target) < Math.abs(best - target)))) {
|
||||
bestScore = remainder;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-mempool-block-view',
|
||||
templateUrl: './mempool-block-view.component.html',
|
||||
styleUrls: ['./mempool-block-view.component.scss']
|
||||
})
|
||||
export class MempoolBlockViewComponent implements OnInit, OnDestroy {
|
||||
autofit: boolean = false;
|
||||
resolution: number = 80;
|
||||
index: number = 0;
|
||||
|
||||
routeParamsSubscription: Subscription;
|
||||
queryParamsSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private websocketService: WebsocketService,
|
||||
public stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||
|
||||
this.routeParamsSubscription = this.route.paramMap
|
||||
.pipe(
|
||||
switchMap((params: ParamMap) => {
|
||||
this.index = parseInt(params.get('index'), 10) || 0;
|
||||
return this.stateService.mempoolBlocks$
|
||||
.pipe(
|
||||
map((blocks) => {
|
||||
if (!blocks.length) {
|
||||
return [{ index: 0, blockSize: 0, blockVSize: 0, feeRange: [0, 0], medianFee: 0, nTx: 0, totalFees: 0 }];
|
||||
}
|
||||
return blocks;
|
||||
}),
|
||||
filter((mempoolBlocks) => mempoolBlocks.length > 0),
|
||||
tap((mempoolBlocks) => {
|
||||
while (!mempoolBlocks[this.index]) {
|
||||
this.index--;
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||
this.autofit = params.autofit === 'true';
|
||||
if (this.autofit) {
|
||||
this.onResize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
if (this.autofit) {
|
||||
this.resolution = bestFitResolution(64, 96, Math.min(window.innerWidth, window.innerHeight));
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.routeParamsSubscription.unsubscribe();
|
||||
this.queryParamsSubscription.unsubscribe();
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import { formatNumber } from '@angular/common';
|
|||
import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
import { feeLevels, chartColors } from '../../app.constants';
|
||||
import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/graphs.utils';
|
||||
|
||||
|
@ -27,7 +27,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||
@Input() data: any[];
|
||||
@Input() filterSize = 100000;
|
||||
@Input() limitFilterFee = 1;
|
||||
@Input() hideCount: boolean = false;
|
||||
@Input() hideCount: boolean = true;
|
||||
@Input() height: number | string = 200;
|
||||
@Input() top: number | string = 20;
|
||||
@Input() right: number | string = 10;
|
||||
|
@ -53,7 +53,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||
chartInstance: any = undefined;
|
||||
weightMode: boolean = false;
|
||||
isWidget: boolean = false;
|
||||
showCount: boolean = true;
|
||||
showCount: boolean = false;
|
||||
|
||||
constructor(
|
||||
private vbytesPipe: VbytesPipe,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ChangeDetectionStrategy, Component, Input, NgZone, OnInit, HostBinding } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { EChartsOption, PieSeriesOption } from 'echarts';
|
||||
import { EChartsOption, PieSeriesOption } from '../../graphs/echarts';
|
||||
import { merge, Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, switchMap, catchError } from 'rxjs/operators';
|
||||
import { PoolStat } from '../../interfaces/node-api.interface';
|
||||
|
@ -127,7 +127,7 @@ export class PoolPreviewComponent implements OnInit {
|
|||
title: title,
|
||||
animation: false,
|
||||
color: [
|
||||
new graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
{ offset: 0, color: '#F4511E' },
|
||||
{ offset: 0.25, color: '#FB8C00' },
|
||||
{ offset: 0.5, color: '#FFB300' },
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||
import { BehaviorSubject, Observable, of, timer } from 'rxjs';
|
||||
import { catchError, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators';
|
||||
import { BlockExtended, PoolStat } from '../../interfaces/node-api.interface';
|
||||
|
@ -131,7 +131,7 @@ export class PoolComponent implements OnInit {
|
|||
title: title,
|
||||
animation: false,
|
||||
color: [
|
||||
new graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [
|
||||
{ offset: 0, color: '#F4511E' },
|
||||
{ offset: 0.25, color: '#FB8C00' },
|
||||
{ offset: 0.5, color: '#FFB300' },
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { PrivacyPolicyComponent } from './privacy-policy.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: PrivacyPolicyComponent,
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class PrivacyPolicyRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
PrivacyPolicyRoutingModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
PrivacyPolicyComponent,
|
||||
]
|
||||
})
|
||||
export class PrivacyPolicyModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ export class StatisticsComponent implements OnInit {
|
|||
chartColors = chartColors;
|
||||
filterSize = 100000;
|
||||
filterFeeIndex = 1;
|
||||
showCount = true;
|
||||
showCount = false;
|
||||
maxFeeIndex: number;
|
||||
dropDownOpen = false;
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { TermsOfServiceComponent } from './terms-of-service.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: TermsOfServiceComponent,
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class TermsModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TermsModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
TermsOfServiceComponent,
|
||||
]
|
||||
})
|
||||
export class TermsOfServiceModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { TrademarkPolicyComponent } from './trademark-policy.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: TrademarkPolicyComponent,
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class TrademarkRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TrademarkRoutingModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
TrademarkPolicyComponent,
|
||||
]
|
||||
})
|
||||
export class TrademarkModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -292,9 +292,9 @@
|
|||
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||
<td [innerHTML]="'‎' + (tx.weight / 4 | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="cpfpInfo && cpfpInfo.adjustedVsize && cpfpInfo.adjustedVsize > (tx.weight / 4)">
|
||||
<tr *ngIf="adjustedVsize != null">
|
||||
<td i18n="transaction.adjusted-vsize|Transaction Adjusted VSize">Adjusted vsize</td>
|
||||
<td [innerHTML]="'‎' + (cpfpInfo.adjustedVsize | vbytes: 2)"></td>
|
||||
<td [innerHTML]="'‎' + (adjustedVsize | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="block.weight">Weight</td>
|
||||
|
@ -314,9 +314,9 @@
|
|||
<td i18n="transaction.locktime">Locktime</td>
|
||||
<td [innerHTML]="'‎' + (tx.locktime | number)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="cpfpInfo && cpfpInfo.adjustedVsize && cpfpInfo.adjustedVsize > (tx.weight / 4)">
|
||||
<tr *ngIf="sigops != null">
|
||||
<td i18n="transaction.sigops|Transaction Sigops">Sigops</td>
|
||||
<td [innerHTML]="'‎' + (cpfpInfo.sigops | number)"></td>
|
||||
<td [innerHTML]="'‎' + (sigops | number)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="transaction.hex">Transaction hex</td>
|
||||
|
|
|
@ -62,6 +62,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
rbfReplaces: string[];
|
||||
rbfInfo: RbfTree;
|
||||
cpfpInfo: CpfpInfo | null;
|
||||
sigops: number | null;
|
||||
adjustedVsize: number | null;
|
||||
showCpfpDetails = false;
|
||||
fetchCpfp$ = new Subject<string>();
|
||||
fetchRbfHistory$ = new Subject<string>();
|
||||
|
@ -343,6 +345,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (tx.fee === undefined) {
|
||||
this.tx.fee = 0;
|
||||
}
|
||||
if (this.tx.sigops != null) {
|
||||
this.sigops = this.tx.sigops;
|
||||
this.adjustedVsize = Math.max(this.tx.weight / 4, this.sigops * 5);
|
||||
}
|
||||
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
|
||||
this.isLoadingTx = false;
|
||||
this.error = undefined;
|
||||
|
@ -422,6 +428,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
this.rbfTransaction = rbfTransaction;
|
||||
this.replaced = true;
|
||||
this.stateService.markBlock$.next({});
|
||||
|
||||
if (rbfTransaction && !this.tx) {
|
||||
this.fetchCachedTx$.next(this.txId);
|
||||
}
|
||||
|
@ -541,6 +549,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
this.cpfpInfo = cpfpInfo;
|
||||
if (this.cpfpInfo.adjustedVsize && this.cpfpInfo.sigops != null) {
|
||||
this.sigops = this.cpfpInfo.sigops;
|
||||
this.adjustedVsize = this.cpfpInfo.adjustedVsize;
|
||||
}
|
||||
this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
|
||||
}
|
||||
|
||||
|
@ -567,6 +579,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.replaced = false;
|
||||
this.transactionTime = -1;
|
||||
this.cpfpInfo = null;
|
||||
this.adjustedVsize = null;
|
||||
this.sigops = null;
|
||||
this.hasEffectiveFeeRate = false;
|
||||
this.rbfInfo = null;
|
||||
this.rbfReplaces = [];
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { TransactionComponent } from './transaction.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { TxBowtieModule } from '../tx-bowtie-graph/tx-bowtie.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':id',
|
||||
component: TransactionComponent,
|
||||
data: {
|
||||
ogImage: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class TransactionRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TransactionRoutingModule,
|
||||
SharedModule,
|
||||
TxBowtieModule,
|
||||
],
|
||||
declarations: [
|
||||
TransactionComponent,
|
||||
]
|
||||
})
|
||||
export class TransactionModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right nowrap amount">
|
||||
<td class="text-right nowrap amount" [class]="{large: vin?.prevout?.value > 1000000000}">
|
||||
<ng-template [ngIf]="vin.prevout && vin.prevout.asset && vin.prevout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
|
||||
<div *ngIf="assetsMinimal && assetsMinimal[vin.prevout.asset] else assetVinNotFound">
|
||||
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vin.prevout }"></ng-container>
|
||||
|
@ -222,7 +222,7 @@
|
|||
</ng-template>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td class="text-right nowrap amount">
|
||||
<td class="text-right nowrap amount" [class]="{large: vout?.value > 1000000000}">
|
||||
<ng-template [ngIf]="vout.asset && vout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
|
||||
<div *ngIf="assetsMinimal && assetsMinimal[vout.asset] else assetNotFound">
|
||||
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vout }"></ng-container>
|
||||
|
|
|
@ -46,7 +46,16 @@
|
|||
}
|
||||
|
||||
td.amount {
|
||||
width: 32.5%;
|
||||
width: 36%;
|
||||
@media (max-width: 576px) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
td.amount.large {
|
||||
width: 45%;
|
||||
@media (max-width: 576px) {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.extra-info {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.inter
|
|||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { AssetsService } from '../../services/assets.service';
|
||||
import { filter, map, tap, switchMap, shareReplay } from 'rxjs/operators';
|
||||
import { filter, map, tap, switchMap, shareReplay, catchError } from 'rxjs/operators';
|
||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { PriceService } from '../../services/price.service';
|
||||
|
@ -75,7 +75,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||
for (let i = 0; i < txIds.length; i += 50) {
|
||||
batches.push(txIds.slice(i, i + 50));
|
||||
}
|
||||
return forkJoin(batches.map(batch => this.apiService.getOutspendsBatched$(batch)));
|
||||
return forkJoin(batches.map(batch => { return this.electrsApiService.cachedRequest(this.electrsApiService.getOutspendsBatched$, 250, batch); }));
|
||||
} else {
|
||||
return of([]);
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||
outspends.forEach((outspend, i) => {
|
||||
transactions[i]._outspends = outspend;
|
||||
});
|
||||
this.ref.markForCheck();
|
||||
}),
|
||||
),
|
||||
this.stateService.utxoSpent$
|
||||
|
@ -108,6 +109,10 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||
.pipe(
|
||||
filter(() => this.stateService.env.LIGHTNING),
|
||||
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
||||
catchError((error) => {
|
||||
// handle 404
|
||||
return of([]);
|
||||
}),
|
||||
tap((channels) => {
|
||||
if (!this.transactions) {
|
||||
return;
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ApiService } from '../../services/api.service';
|
|||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { AssetsService } from '../../services/assets.service';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
|
||||
interface SvgLine {
|
||||
path: string;
|
||||
|
@ -100,7 +101,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||
private router: Router,
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private assetsService: AssetsService,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
) {
|
||||
|
@ -123,7 +124,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||
.pipe(
|
||||
switchMap((txid) => {
|
||||
if (!this.cached) {
|
||||
return this.apiService.getOutspendsBatched$([txid]);
|
||||
return this.electrsApiService.cachedRequest(this.electrsApiService.getOutspendsBatched$, 250, [txid]);
|
||||
} else {
|
||||
return of(null);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { TxBowtieGraphComponent } from '../tx-bowtie-graph/tx-bowtie-graph.component';
|
||||
import { TxBowtieGraphTooltipComponent } from '../tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
TxBowtieGraphComponent,
|
||||
TxBowtieGraphTooltipComponent,
|
||||
],
|
||||
exports: [
|
||||
TxBowtieGraphComponent,
|
||||
TxBowtieGraphTooltipComponent,
|
||||
]
|
||||
})
|
||||
export class TxBowtieModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -137,8 +137,16 @@
|
|||
|
||||
<ng-template type="what-is-a-mempool-explorer">
|
||||
<p>A mempool explorer is a tool that enables you to view real-time and historical information about a node's mempool, visualize its transactions, and search and view those transactions.</p><p>The mempool.space website invented the concept of visualizing a Bitcoin node's mempool as <b>projected blocks</b>. These blocks are the inspiration for our half-filled block logo.</p><p>Projected blocks are on the left of the dotted white line, and confirmed blocks are on the right.</p>
|
||||
<div class="blockchain-wrapper">
|
||||
<app-blockchain></app-blockchain>
|
||||
<div class="blockchain-wrapper" [dir]="timeLtr ? 'rtl' : 'ltr'" [class.time-ltr]="timeLtr">
|
||||
<div class="position-container">
|
||||
<span>
|
||||
<div class="blocks-wrapper">
|
||||
<app-mempool-blocks></app-mempool-blocks>
|
||||
<app-blockchain-blocks></app-blockchain-blocks>
|
||||
</div>
|
||||
<div id="divider"></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
|
|
@ -259,13 +259,46 @@ h3 {
|
|||
}
|
||||
|
||||
.blockchain-wrapper {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
min-height: 220px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.position-container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 150px;
|
||||
}
|
||||
|
||||
#divider {
|
||||
width: 2px;
|
||||
height: 175px;
|
||||
left: 0;
|
||||
top: -40px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&.time-ltr {
|
||||
.blocks-wrapper {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.blockchain-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
|
||||
:host-context(.ltr-layout) {
|
||||
.blockchain-wrapper.time-ltr .blocks-wrapper,
|
||||
.blockchain-wrapper .blocks-wrapper {
|
||||
direction: ltr;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.rtl-layout) {
|
||||
.blockchain-wrapper.time-ltr .blocks-wrapper,
|
||||
.blockchain-wrapper .blocks-wrapper {
|
||||
direction: rtl;
|
||||
}
|
||||
}
|
||||
|
||||
#disclaimer {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, OnInit, Input, QueryList, AfterViewInit, ViewChildren } from '@angular/core';
|
||||
import { Env, StateService } from '../../services/state.service';
|
||||
import { Observable, merge, of, Subject } from 'rxjs';
|
||||
import { Observable, merge, of, Subject, Subscription } from 'rxjs';
|
||||
import { tap, takeUntil } from 'rxjs/operators';
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { faqData, restApiDocsData, wsApiDocsData } from './api-docs-data';
|
||||
|
@ -30,6 +30,8 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
|||
officialMempoolInstance: boolean;
|
||||
auditEnabled: boolean;
|
||||
mobileViewport: boolean = false;
|
||||
timeLtrSubscription: Subscription;
|
||||
timeLtr: boolean = this.stateService.timeLtr.value;
|
||||
|
||||
@ViewChildren(FaqTemplateDirective) faqTemplates: QueryList<FaqTemplateDirective>;
|
||||
dict = {};
|
||||
|
@ -104,12 +106,17 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
|||
this.electrsPort = 51302; break;
|
||||
}
|
||||
});
|
||||
|
||||
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||
this.timeLtr = !!ltr;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next(true);
|
||||
this.destroy$.complete();
|
||||
window.removeEventListener('scroll', this.onDocScroll);
|
||||
this.timeLtrSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onDocScroll() {
|
||||
|
|
17
frontend/src/app/graphs/echarts.ts
Normal file
17
frontend/src/app/graphs/echarts.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Import tree-shakeable echarts
|
||||
import * as echarts from 'echarts/core';
|
||||
import { LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart } from 'echarts/charts';
|
||||
import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, GeoComponent, DataZoomComponent, VisualMapComponent, MarkLineComponent } from 'echarts/components';
|
||||
import { SVGRenderer, CanvasRenderer } from 'echarts/renderers';
|
||||
// Typescript interfaces
|
||||
import { EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption } from 'echarts';
|
||||
|
||||
|
||||
echarts.use([
|
||||
SVGRenderer, CanvasRenderer,
|
||||
TitleComponent, TooltipComponent, GridComponent,
|
||||
LegendComponent, GeoComponent, DataZoomComponent,
|
||||
VisualMapComponent, MarkLineComponent,
|
||||
LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart
|
||||
]);
|
||||
export { echarts, EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption };
|
|
@ -53,7 +53,7 @@ import { CommonModule } from '@angular/common';
|
|||
SharedModule,
|
||||
GraphsRoutingModule,
|
||||
NgxEchartsModule.forRoot({
|
||||
echarts: () => import('echarts')
|
||||
echarts: () => import('./echarts').then(m => m.echarts),
|
||||
})
|
||||
],
|
||||
exports: [
|
||||
|
|
|
@ -8,8 +8,6 @@ import { BlockSizesWeightsGraphComponent } from '../components/block-sizes-weigh
|
|||
import { GraphsComponent } from '../components/graphs/graphs.component';
|
||||
import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component';
|
||||
import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.component';
|
||||
import { LiquidMasterPageComponent } from '../components/liquid-master-page/liquid-master-page.component';
|
||||
import { MasterPageComponent } from '../components/master-page/master-page.component';
|
||||
import { MempoolBlockComponent } from '../components/mempool-block/mempool-block.component';
|
||||
import { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component';
|
||||
import { PoolRankingComponent } from '../components/pool-ranking/pool-ranking.component';
|
||||
|
@ -18,22 +16,10 @@ import { StartComponent } from '../components/start/start.component';
|
|||
import { StatisticsComponent } from '../components/statistics/statistics.component';
|
||||
import { TelevisionComponent } from '../components/television/television.component';
|
||||
import { DashboardComponent } from '../dashboard/dashboard.component';
|
||||
import { NodesNetworksChartComponent } from '../lightning/nodes-networks-chart/nodes-networks-chart.component';
|
||||
import { LightningStatisticsChartComponent } from '../lightning/statistics-chart/lightning-statistics-chart.component';
|
||||
import { NodesPerISPChartComponent } from '../lightning/nodes-per-isp-chart/nodes-per-isp-chart.component';
|
||||
import { NodesPerCountryChartComponent } from '../lightning/nodes-per-country-chart/nodes-per-country-chart.component';
|
||||
import { NodesMap } from '../lightning/nodes-map/nodes-map.component';
|
||||
import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels-map.component';
|
||||
|
||||
const browserWindow = window || {};
|
||||
// @ts-ignore
|
||||
const browserWindowEnv = browserWindow.__env || {};
|
||||
const isLiquid = browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: isLiquid ? LiquidMasterPageComponent : MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'mining/pool/:slug',
|
||||
|
@ -108,34 +94,9 @@ const routes: Routes = [
|
|||
component: BlockSizesWeightsGraphComponent,
|
||||
},
|
||||
{
|
||||
path: 'lightning/nodes-networks',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesNetworksChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'lightning/capacity',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: LightningStatisticsChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'lightning/nodes-per-isp',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesPerISPChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'lightning/nodes-per-country',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesPerCountryChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'lightning/nodes-map',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesMap,
|
||||
},
|
||||
{
|
||||
path: 'lightning/nodes-channels-map',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesChannelsMap,
|
||||
path: 'lightning',
|
||||
data: { preload: true, networks: ['bitcoin'] },
|
||||
loadChildren: () => import ('./lightning-graphs.module').then(m => m.LightningGraphsModule),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
|
|
58
frontend/src/app/graphs/lightning-graphs.module.ts
Normal file
58
frontend/src/app/graphs/lightning-graphs.module.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { NodesNetworksChartComponent } from '../lightning/nodes-networks-chart/nodes-networks-chart.component';
|
||||
import { LightningStatisticsChartComponent } from '../lightning/statistics-chart/lightning-statistics-chart.component';
|
||||
import { NodesPerISPChartComponent } from '../lightning/nodes-per-isp-chart/nodes-per-isp-chart.component';
|
||||
import { NodesPerCountryChartComponent } from '../lightning/nodes-per-country-chart/nodes-per-country-chart.component';
|
||||
import { NodesMap } from '../lightning/nodes-map/nodes-map.component';
|
||||
import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels-map.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'nodes-networks',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesNetworksChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'capacity',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: LightningStatisticsChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'nodes-per-isp',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesPerISPChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'nodes-per-country',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesPerCountryChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'nodes-map',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesMap,
|
||||
},
|
||||
{
|
||||
path: 'nodes-channels-map',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: NodesChannelsMap,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class LightningGraphsRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
LightningGraphsRoutingModule,
|
||||
],
|
||||
})
|
||||
export class LightningGraphsModule { }
|
|
@ -26,6 +26,7 @@ export interface Transaction {
|
|||
_outspends?: Outspend[];
|
||||
_channels?: TransactionChannels;
|
||||
price?: Price;
|
||||
sigops?: number;
|
||||
}
|
||||
|
||||
export interface TransactionChannels {
|
||||
|
|
|
@ -57,7 +57,7 @@ export class GroupPreviewComponent implements OnInit {
|
|||
return of(null);
|
||||
}
|
||||
|
||||
return this.lightningApiService.getNodGroupNodes$(this.groupId);
|
||||
return this.lightningApiService.getNodeGroup$(this.groupId);
|
||||
}),
|
||||
map((nodes) => {
|
||||
for (const node of nodes) {
|
||||
|
|
|
@ -41,7 +41,7 @@ export class GroupComponent implements OnInit {
|
|||
this.seoService.setTitle(`Mempool.space Lightning Nodes`);
|
||||
this.seoService.setDescription(`See all Lightning nodes run by mempool.space -- these are the nodes that provide the data on the mempool.space Lightning dashboard.`);
|
||||
|
||||
this.nodes$ = this.lightningApiService.getNodGroupNodes$('mempool.space')
|
||||
this.nodes$ = this.lightningApiService.getNodeGroup$('mempool.space')
|
||||
.pipe(
|
||||
map((nodes) => {
|
||||
for (const node of nodes) {
|
||||
|
|
|
@ -27,7 +27,7 @@ export class LightningApiService {
|
|||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey);
|
||||
}
|
||||
|
||||
getNodGroupNodes$(name: string): Observable<any[]> {
|
||||
getNodeGroup$(name: string): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(this.apiBasePath + '/api/v1/lightning/nodes/group/' + name);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { download } from '../../shared/graphs.utils';
|
||||
import { LightningApiService } from '../lightning-api.service';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { formatNumber } from '@angular/common';
|
||||
|
|
|
@ -6,8 +6,7 @@ import { AssetsService } from '../../services/assets.service';
|
|||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { EChartsOption, registerMap } from 'echarts';
|
||||
import 'echarts-gl';
|
||||
import { EChartsOption, echarts } from '../../graphs/echarts';
|
||||
import { isMobile } from '../../shared/common.utils';
|
||||
|
||||
@Component({
|
||||
|
@ -88,7 +87,7 @@ export class NodesChannelsMap implements OnInit {
|
|||
this.style !== 'channelpage' ? this.apiService.getChannelsGeo$(params.get('public_key') ?? undefined, this.style) : [''],
|
||||
[params.get('public_key') ?? undefined]
|
||||
).pipe(tap((data) => {
|
||||
registerMap('world', data[0]);
|
||||
echarts.registerMap('world', data[0]);
|
||||
|
||||
const channelsLoc = [];
|
||||
const nodes = [];
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { formatNumber } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnChanges } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ECharts, EChartsOption, TreemapSeriesOption } from 'echarts';
|
||||
import { EChartsOption, TreemapSeriesOption } from '../../graphs/echarts';
|
||||
import { Observable, share, switchMap, tap } from 'rxjs';
|
||||
import { lerpColor } from '../../shared/graphs.utils';
|
||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
|
||||
|
@ -18,7 +18,7 @@ import { StateService } from '../../services/state.service';
|
|||
export class NodeChannels implements OnChanges {
|
||||
@Input() publicKey: string;
|
||||
|
||||
chartInstance: ECharts;
|
||||
chartInstance: any;
|
||||
chartOptions: EChartsOption = {};
|
||||
chartInitOptions = {
|
||||
renderer: 'svg',
|
||||
|
@ -129,7 +129,7 @@ export class NodeChannels implements OnChanges {
|
|||
};
|
||||
}
|
||||
|
||||
onChartInit(ec: ECharts): void {
|
||||
onChartInit(ec: any): void {
|
||||
this.chartInstance = ec;
|
||||
|
||||
this.chartInstance.on('click', (e) => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { SeoService } from '../../services/seo.service';
|
|||
import { ApiService } from '../../services/api.service';
|
||||
import { Observable, BehaviorSubject, switchMap, tap, combineLatest } from 'rxjs';
|
||||
import { AssetsService } from '../../services/assets.service';
|
||||
import { EChartsOption, registerMap } from 'echarts';
|
||||
import { EChartsOption, echarts } from '../../graphs/echarts';
|
||||
import { lerpColor } from '../../shared/graphs.utils';
|
||||
import { Router } from '@angular/router';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
|
@ -63,7 +63,7 @@ export class NodesMap implements OnInit, OnChanges {
|
|||
this.assetsService.getWorldMapJson$,
|
||||
this.nodes$
|
||||
).pipe(tap((data) => {
|
||||
registerMap('world', data[0]);
|
||||
echarts.registerMap('world', data[0]);
|
||||
|
||||
let maxLiquidity = data[1].maxLiquidity;
|
||||
let inputNodes: any[] = data[1].nodes;
|
||||
|
@ -88,7 +88,7 @@ export class NodesMap implements OnInit, OnChanges {
|
|||
node.public_key,
|
||||
node.alias,
|
||||
node.capacity,
|
||||
node.channels,
|
||||
node.active_channel_count,
|
||||
node.country,
|
||||
node.iso_code,
|
||||
]);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
|
||||
import { EChartsOption, graphic, LineSeriesOption} from 'echarts';
|
||||
import { echarts, EChartsOption, LineSeriesOption } from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { formatNumber } from '@angular/common';
|
||||
|
@ -152,7 +152,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||
opacity: 0.5,
|
||||
},
|
||||
stack: 'Total',
|
||||
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
{ offset: 0, color: '#D81B60' },
|
||||
{ offset: 1, color: '#D81B60AA' },
|
||||
]),
|
||||
|
@ -174,7 +174,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||
opacity: 0.5,
|
||||
},
|
||||
stack: 'Total',
|
||||
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
{ offset: 0, color: '#be7d4c' },
|
||||
{ offset: 1, color: '#be7d4cAA' },
|
||||
]),
|
||||
|
@ -195,7 +195,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||
opacity: 0.5,
|
||||
},
|
||||
stack: 'Total',
|
||||
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
{ offset: 0, color: '#FFB300' },
|
||||
{ offset: 1, color: '#FFB300AA' },
|
||||
]),
|
||||
|
@ -216,7 +216,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||
opacity: 0.5,
|
||||
},
|
||||
stack: 'Total',
|
||||
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
{ offset: 0, color: '#7D4698' },
|
||||
{ offset: 1, color: '#7D4698AA' },
|
||||
]),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { EChartsOption, PieSeriesOption } from 'echarts';
|
||||
import { EChartsOption, PieSeriesOption } from '../../graphs/echarts';
|
||||
import { map, Observable, share, tap } from 'rxjs';
|
||||
import { chartColors } from '../../app.constants';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { EChartsOption, PieSeriesOption } from 'echarts';
|
||||
import { EChartsOption, PieSeriesOption } from '../../graphs/echarts';
|
||||
import { combineLatest, map, Observable, share, startWith, Subject, switchMap, tap } from 'rxjs';
|
||||
import { chartColors } from '../../app.constants';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
|
@ -132,7 +132,7 @@ export class LightningStatisticsChartComponent implements OnInit {
|
|||
animation: false,
|
||||
color: [
|
||||
'#FFB300',
|
||||
new graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||
{ offset: 0, color: '#D81B60' },
|
||||
{ offset: 1, color: '#D81B60AA' },
|
||||
]),
|
||||
|
|
37
frontend/src/app/liquid/liquid-graphs.module.ts
Normal file
37
frontend/src/app/liquid/liquid-graphs.module.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LiquidMasterPageComponent } from '../components/liquid-master-page/liquid-master-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LiquidMasterPageComponent,
|
||||
loadChildren: () => import('../graphs/graphs.module').then(m => m.GraphsModule),
|
||||
data: { preload: true },
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class LiquidGraphsRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
LiquidGraphsRoutingModule,
|
||||
],
|
||||
})
|
||||
export class LiquidGraphsModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
125
frontend/src/app/liquid/liquid-master-page.module.ts
Normal file
125
frontend/src/app/liquid/liquid-master-page.module.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { LiquidMasterPageComponent } from '../components/liquid-master-page/liquid-master-page.component';
|
||||
|
||||
import { StartComponent } from '../components/start/start.component';
|
||||
import { AddressComponent } from '../components/address/address.component';
|
||||
import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
|
||||
import { BlocksList } from '../components/blocks-list/blocks-list.component';
|
||||
import { AssetGroupComponent } from '../components/assets/asset-group/asset-group.component';
|
||||
import { AssetsComponent } from '../components/assets/assets.component';
|
||||
import { AssetComponent } from '../components/asset/asset.component';
|
||||
import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LiquidMasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
loadChildren: () => import('../components/about/about.module').then(m => m.AboutModule),
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
loadChildren: () => import('../components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule),
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
loadChildren: () => import('../components/privacy-policy/privacy-policy.module').then(m => m.PrivacyPolicyModule),
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
loadChildren: () => import('../components/trademark-policy/trademark-policy.module').then(m => m.TrademarkModule),
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent,
|
||||
data: {
|
||||
ogImage: true,
|
||||
networkSpecific: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tx',
|
||||
component: StartComponent,
|
||||
data: { preload: true, networkSpecific: true },
|
||||
loadChildren: () => import('../components/transaction/transaction.module').then(m => m.TransactionModule),
|
||||
},
|
||||
{
|
||||
path: 'block',
|
||||
component: StartComponent,
|
||||
data: { preload: true, networkSpecific: true },
|
||||
loadChildren: () => import('../components/block/block.module').then(m => m.BlockModule),
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
data: { networks: ['liquid'] },
|
||||
component: AssetsNavComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'all',
|
||||
data: { networks: ['liquid'] },
|
||||
component: AssetsComponent,
|
||||
},
|
||||
{
|
||||
path: 'asset/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: AssetComponent
|
||||
},
|
||||
{
|
||||
path: 'group/:id',
|
||||
data: { networkSpecific: true },
|
||||
component: AssetGroupComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'all'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('../docs/docs.module').then(m => m.DocsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('../docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class LiquidRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
LiquidRoutingModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
LiquidMasterPageComponent,
|
||||
]
|
||||
})
|
||||
export class LiquidMasterPageModule { }
|
120
frontend/src/app/master-page.module.ts
Normal file
120
frontend/src/app/master-page.module.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
|
||||
import { StartComponent } from './components/start/start.component';
|
||||
import { AddressComponent } from './components/address/address.component';
|
||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
|
||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||
import { RbfList } from './components/rbf-list/rbf-list.component';
|
||||
|
||||
const browserWindow = window || {};
|
||||
// @ts-ignore
|
||||
const browserWindowEnv = browserWindow.__env || {};
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MasterPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'mining/blocks',
|
||||
redirectTo: 'blocks',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'tx/push',
|
||||
component: PushTransactionComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
loadChildren: () => import('./components/about/about.module').then(m => m.AboutModule),
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'rbf',
|
||||
component: RbfList,
|
||||
},
|
||||
{
|
||||
path: 'terms-of-service',
|
||||
loadChildren: () => import('./components/terms-of-service/terms-of-service.module').then(m => m.TermsOfServiceModule),
|
||||
},
|
||||
{
|
||||
path: 'privacy-policy',
|
||||
loadChildren: () => import('./components/privacy-policy/privacy-policy.module').then(m => m.PrivacyPolicyModule),
|
||||
},
|
||||
{
|
||||
path: 'trademark-policy',
|
||||
loadChildren: () => import('./components/trademark-policy/trademark-policy.module').then(m => m.TrademarkModule),
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent,
|
||||
data: {
|
||||
ogImage: true,
|
||||
networkSpecific: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tx',
|
||||
component: StartComponent,
|
||||
data: { preload: true, networkSpecific: true },
|
||||
loadChildren: () => import('./components/transaction/transaction.module').then(m => m.TransactionModule),
|
||||
},
|
||||
{
|
||||
path: 'block',
|
||||
component: StartComponent,
|
||||
data: { preload: true, networkSpecific: true },
|
||||
loadChildren: () => import('./components/block/block.module').then(m => m.BlockModule),
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
|
||||
},
|
||||
{
|
||||
path: 'lightning',
|
||||
loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule),
|
||||
data: { preload: browserWindowEnv && browserWindowEnv.LIGHTNING === true, networks: ['bitcoin'] },
|
||||
},
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class MasterPageRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MasterPageRoutingModule,
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
MasterPageComponent,
|
||||
]
|
||||
})
|
||||
export class MasterPageModule { }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -9,6 +9,7 @@ import { BlockPreviewComponent } from './components/block/block-preview.componen
|
|||
import { AddressPreviewComponent } from './components/address/address-preview.component';
|
||||
import { PoolPreviewComponent } from './components/pool/pool-preview.component';
|
||||
import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
|
||||
import { TxBowtieModule } from './components/tx-bowtie-graph/tx-bowtie.module';
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TransactionPreviewComponent,
|
||||
|
@ -23,6 +24,7 @@ import { MasterPagePreviewComponent } from './components/master-page-preview/mas
|
|||
RouterModule,
|
||||
PreviewsRoutingModule,
|
||||
GraphsModule,
|
||||
TxBowtieModule,
|
||||
],
|
||||
})
|
||||
export class PreviewsModule { }
|
||||
|
|
|
@ -31,7 +31,8 @@ const routes: Routes = [
|
|||
},
|
||||
{
|
||||
path: 'lightning',
|
||||
loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
|
||||
loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule),
|
||||
data: { preload: true },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
|
||||
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit } from '../interfaces/node-api.interface';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface';
|
||||
import { Outspend, Transaction } from '../interfaces/electrs.interface';
|
||||
|
@ -20,6 +20,8 @@ export class ApiService {
|
|||
private apiBaseUrl: string; // base URL is protocol, hostname, and port
|
||||
private apiBasePath: string; // network path is /testnet, etc. or '' for mainnet
|
||||
|
||||
private requestCache = new Map<string, { subject: BehaviorSubject<any>, expiry: number }>;
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private stateService: StateService,
|
||||
|
@ -44,6 +46,46 @@ export class ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
private generateCacheKey(functionName: string, params: any[]): string {
|
||||
return functionName + JSON.stringify(params);
|
||||
}
|
||||
|
||||
// delete expired cache entries
|
||||
private cleanExpiredCache(): void {
|
||||
this.requestCache.forEach((value, key) => {
|
||||
if (value.expiry < Date.now()) {
|
||||
this.requestCache.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cachedRequest<T, F extends (...args: any[]) => Observable<T>>(
|
||||
apiFunction: F,
|
||||
expireAfter: number, // in ms
|
||||
...params: Parameters<F>
|
||||
): Observable<T> {
|
||||
this.cleanExpiredCache();
|
||||
|
||||
const cacheKey = this.generateCacheKey(apiFunction.name, params);
|
||||
if (!this.requestCache.has(cacheKey)) {
|
||||
const subject = new BehaviorSubject<T | null>(null);
|
||||
this.requestCache.set(cacheKey, { subject, expiry: Date.now() + expireAfter });
|
||||
|
||||
apiFunction.bind(this)(...params).pipe(
|
||||
tap(data => {
|
||||
subject.next(data as T);
|
||||
}),
|
||||
catchError((error) => {
|
||||
subject.error(error);
|
||||
return of(null);
|
||||
}),
|
||||
shareReplay(1),
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
return this.requestCache.get(cacheKey).subject.asObservable().pipe(filter(val => val !== null), take(1));
|
||||
}
|
||||
|
||||
list2HStatistics$(): Observable<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/2h');
|
||||
}
|
||||
|
@ -96,14 +138,6 @@ export class ApiService {
|
|||
return this.httpClient.get<number[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/transaction-times', { params });
|
||||
}
|
||||
|
||||
getOutspendsBatched$(txIds: string[]): Observable<Outspend[][]> {
|
||||
let params = new HttpParams();
|
||||
txIds.forEach((txId: string) => {
|
||||
params = params.append('txId[]', txId);
|
||||
});
|
||||
return this.httpClient.get<Outspend[][]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/outspends', { params });
|
||||
}
|
||||
|
||||
getAboutPageProfiles$(): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/services/sponsors');
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue