diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 832efcbbf..0cd95e568 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -1,7 +1,10 @@ name: Cypress Tests -on: [push, pull_request] - +on: + push: + branches: + - master + pull_request: jobs: cypress: runs-on: ${{ matrix.os }} @@ -24,9 +27,10 @@ jobs: - name: ${{ matrix.browser }} browser tests (Mempool) uses: cypress-io/github-action@v2 with: + tag: ${{ github.event_name }} working-directory: frontend build: npm run config:defaults:mempool - start: npm run start:local-prod + start: npm run start:local-staging wait-on: 'http://localhost:4200' wait-on-timeout: 120 record: true @@ -39,6 +43,7 @@ jobs: browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} @@ -47,9 +52,10 @@ jobs: uses: cypress-io/github-action@v2 if: always() with: + tag: ${{ github.event_name }} working-directory: frontend build: npm run config:defaults:liquid - start: npm run start:local-prod + start: npm run start:local-staging wait-on: 'http://localhost:4200' wait-on-timeout: 120 record: true @@ -61,6 +67,7 @@ jobs: browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} @@ -69,9 +76,10 @@ jobs: uses: cypress-io/github-action@v2 if: always() with: + tag: ${{ github.event_name }} working-directory: frontend build: npm run config:defaults:bisq - start: npm run start:local-prod + start: npm run start:local-staging wait-on: 'http://localhost:4200' wait-on-timeout: 120 record: true @@ -81,6 +89,7 @@ jobs: browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' env: + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} diff --git a/README.md b/README.md index 34dca4862..af4ae224f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# The Mempool Open Source Projectâ„¢ +# The Mempool Open Source Projectâ„¢ [![mempool](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/ry4br7/master&style=flat-square)](https://dashboard.cypress.io/projects/ry4br7/runs) Mempool is the fully featured visualizer, explorer, and API service running on [mempool.space](https://mempool.space/), an open source project developed and operated for the benefit of the Bitcoin community, with a focus on the emerging transaction fee market to help our transition into a multi-layer ecosystem. @@ -88,9 +88,10 @@ JSON: "BLOCK_WEIGHT_UNITS": 4000000, "INITIAL_BLOCKS_AMOUNT": 8, "MEMPOOL_BLOCKS_AMOUNT": 8, - "PRICE_FEED_UPDATE_INTERVAL": 3600, + "PRICE_FEED_UPDATE_INTERVAL": 600, "USE_SECOND_NODE_FOR_MINFEE": false, - "EXTERNAL_ASSETS": [] + "EXTERNAL_ASSETS": ["https://mempool.space/resources/pools.json"], + "STDOUT_LOG_MIN_PRIORITY": "debug" }, ``` @@ -111,6 +112,7 @@ docker-compose overrides:: MEMPOOL_PRICE_FEED_UPDATE_INTERVAL: "" MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: "" MEMPOOL_EXTERNAL_ASSETS: "" + MEMPOOL_STDOUT_LOG_MIN_PRIORITY: "" ``` JSON: @@ -245,6 +247,39 @@ docker-compose overrides: BISQ_DATA_PATH: "" ``` +JSON: +``` + "SOCKS5PROXY": { + "ENABLED": false, + "HOST": "127.0.0.1", + "PORT": "9050", + "USERNAME": "", + "PASSWORD": "" + } +``` + +docker-compose overrides: +``` + SOCKS5PROXY_ENABLED: "" + SOCKS5PROXY_HOST: "" + SOCKS5PROXY_PORT: "" + SOCKS5PROXY_USERNAME: "" + SOCKS5PROXY_PASSWORD: "" +``` + +JSON: +``` + "PRICE_DATA_SERVER": { + "TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices", + "CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices" + } +``` + +docker-compose overrides: +``` + PRICE_DATA_SERVER_TOR_URL: "" + PRICE_DATA_SERVER_CLEARNET_URL: "" +``` # Manual Installation diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index 8a9295b3a..5c45838ee 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -13,11 +13,12 @@ "INITIAL_BLOCKS_AMOUNT": 8, "MEMPOOL_BLOCKS_AMOUNT": 8, "INDEXING_BLOCKS_AMOUNT": 1100, - "PRICE_FEED_UPDATE_INTERVAL": 3600, + "PRICE_FEED_UPDATE_INTERVAL": 600, "USE_SECOND_NODE_FOR_MINFEE": false, "EXTERNAL_ASSETS": [ "https://mempool.space/resources/pools.json" - ] + ], + "STDOUT_LOG_MIN_PRIORITY": "debug" }, "CORE_RPC": { "HOST": "127.0.0.1", @@ -61,5 +62,16 @@ "BISQ": { "ENABLED": false, "DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db" + }, + "SOCKS5PROXY": { + "ENABLED": false, + "HOST": "127.0.0.1", + "PORT": 9050, + "USERNAME": "", + "PASSWORD": "" + }, + "PRICE_DATA_SERVER": { + "TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices", + "CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices" } } diff --git a/backend/package-lock.json b/backend/package-lock.json index a93e977a3..f2ef1abb3 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,6 +19,7 @@ "locutus": "^2.0.12", "mysql2": "2.3.3", "node-worker-threads-pool": "^1.4.3", + "socks-proxy-agent": "^6.1.1", "typescript": "4.4.4", "ws": "8.3.0" }, @@ -181,6 +182,38 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -731,6 +764,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1189,6 +1227,62 @@ "sha.js": "bin.js" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1538,6 +1632,29 @@ "negotiator": "0.6.2" } }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1992,6 +2109,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2352,6 +2474,45 @@ "safe-buffer": "^5.0.1" } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", diff --git a/backend/package.json b/backend/package.json index a5c9a32ff..e72d163b2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -38,6 +38,7 @@ "locutus": "^2.0.12", "mysql2": "2.3.3", "node-worker-threads-pool": "^1.4.3", + "socks-proxy-agent": "^6.1.1", "typescript": "4.4.4", "ws": "8.3.0" }, diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 22bea2480..cd72d91e0 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -96,14 +96,20 @@ class Blocks { */ private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended { const blockExtended: BlockExtended = Object.assign({}, block); - blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0); - blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); + + blockExtended.extras = { + reward: transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0), + coinbaseTx: transactionUtils.stripCoinbaseTransaction(transactions[0]), + }; const transactionsTmp = [...transactions]; transactionsTmp.shift(); transactionsTmp.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize); - blockExtended.medianFee = transactionsTmp.length > 0 ? Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0; - blockExtended.feeRange = transactionsTmp.length > 0 ? Common.getFeesInRange(transactionsTmp, 8) : [0, 0]; + + blockExtended.extras.medianFee = transactionsTmp.length > 0 ? + Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0; + blockExtended.extras.feeRange = transactionsTmp.length > 0 ? + Common.getFeesInRange(transactionsTmp, 8) : [0, 0]; return blockExtended; } @@ -197,7 +203,14 @@ class Blocks { const block = await bitcoinApi.$getBlock(blockHash); const transactions = await this.$getTransactionsExtended(blockHash, block.height, true); const blockExtended = this.getBlockExtended(block, transactions); - const miner = await this.$findBlockMiner(blockExtended.coinbaseTx); + + let miner: PoolTag; + if (blockExtended?.extras?.coinbaseTx) { + miner = await this.$findBlockMiner(blockExtended.extras.coinbaseTx); + } else { + miner = await poolsRepository.$getUnknownPool(); + } + const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true); await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner); } catch (e) { @@ -262,7 +275,12 @@ class Blocks { const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true); if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === true) { - const miner = await this.$findBlockMiner(blockExtended.coinbaseTx); + let miner: PoolTag; + if (blockExtended?.extras?.coinbaseTx) { + miner = await this.$findBlockMiner(blockExtended.extras.coinbaseTx); + } else { + miner = await poolsRepository.$getUnknownPool(); + } await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner); } diff --git a/backend/src/api/fiat-conversion.ts b/backend/src/api/fiat-conversion.ts index d779e31fd..f28f34a0b 100644 --- a/backend/src/api/fiat-conversion.ts +++ b/backend/src/api/fiat-conversion.ts @@ -1,7 +1,9 @@ import logger from '../logger'; -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { IConversionRates } from '../mempool.interfaces'; import config from '../config'; +import backendInfo from './backend-info'; +import { SocksProxyAgent } from 'socks-proxy-agent'; class FiatConversion { private conversionRates: IConversionRates = { @@ -17,6 +19,11 @@ class FiatConversion { public startService() { logger.info('Starting currency rates service'); + if (config.SOCKS5PROXY.ENABLED) { + logger.info(`Currency rates service will be queried over the Tor network using ${config.PRICE_DATA_SERVER.TOR_URL}`); + } else { + logger.info(`Currency rates service will be queried over clearnet using ${config.PRICE_DATA_SERVER.CLEARNET_URL}`); + } setInterval(this.updateCurrency.bind(this), 1000 * config.MEMPOOL.PRICE_FEED_UPDATE_INTERVAL); this.updateCurrency(); } @@ -26,12 +33,43 @@ class FiatConversion { } private async updateCurrency(): Promise { + const headers = { 'User-Agent': `mempool/v${backendInfo.getBackendInfo().version}` }; + let fiatConversionUrl: string; + let response: AxiosResponse; + try { - const response = await axios.get('https://price.bisq.wiz.biz/getAllMarketPrices', { timeout: 10000 }); + if (config.SOCKS5PROXY.ENABLED) { + let socksOptions: any = { + agentOptions: { + keepAlive: true, + }, + host: config.SOCKS5PROXY.HOST, + port: config.SOCKS5PROXY.PORT + }; + + if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) { + socksOptions.username = config.SOCKS5PROXY.USERNAME; + socksOptions.password = config.SOCKS5PROXY.PASSWORD; + } + + const agent = new SocksProxyAgent(socksOptions); + fiatConversionUrl = config.PRICE_DATA_SERVER.TOR_URL; + logger.debug('Querying currency rates service...'); + response = await axios.get(fiatConversionUrl, { httpAgent: agent, headers: headers, timeout: 30000 }); + } else { + fiatConversionUrl = config.PRICE_DATA_SERVER.CLEARNET_URL; + logger.debug('Querying currency rates service...'); + response = await axios.get(fiatConversionUrl, { headers: headers, timeout: 10000 }); + } + const usd = response.data.data.find((item: any) => item.currencyCode === 'USD'); + this.conversionRates = { 'USD': usd.price, }; + + logger.debug(`USD Conversion Rate: ${usd.price}`); + if (this.ratesChangedCallback) { this.ratesChangedCallback(this.conversionRates); } diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 7891a606a..53d74925d 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -380,7 +380,9 @@ class WebsocketHandler { mBlocks = mempoolBlocks.getMempoolBlocks(); } - block.matchRate = matchRate; + if (block.extras) { + block.extras.matchRate = matchRate; + } this.wss.clients.forEach((client) => { if (client.readyState !== WebSocket.OPEN) { diff --git a/backend/src/config.ts b/backend/src/config.ts index 085d538c4..97c3bb32a 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -18,6 +18,7 @@ interface IConfig { PRICE_FEED_UPDATE_INTERVAL: number; USE_SECOND_NODE_FOR_MINFEE: boolean; EXTERNAL_ASSETS: string[]; + STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug'; }; ESPLORA: { REST_API_URL: string; @@ -51,7 +52,7 @@ interface IConfig { ENABLED: boolean; HOST: string; PORT: number; - MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' |'warn' | 'notice' | 'info' | 'debug'; + MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug'; FACILITY: string; }; STATISTICS: { @@ -62,6 +63,17 @@ interface IConfig { ENABLED: boolean; DATA_PATH: string; }; + SOCKS5PROXY: { + ENABLED: boolean; + HOST: string; + PORT: number; + USERNAME: string; + PASSWORD: string; + }; + PRICE_DATA_SERVER: { + TOR_URL: string; + CLEARNET_URL: string; + }; } const defaults: IConfig = { @@ -79,11 +91,12 @@ const defaults: IConfig = { 'INITIAL_BLOCKS_AMOUNT': 8, 'MEMPOOL_BLOCKS_AMOUNT': 8, 'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks - 'PRICE_FEED_UPDATE_INTERVAL': 3600, + 'PRICE_FEED_UPDATE_INTERVAL': 600, 'USE_SECOND_NODE_FOR_MINFEE': false, 'EXTERNAL_ASSETS': [ 'https://mempool.space/resources/pools.json' - ] + ], + 'STDOUT_LOG_MIN_PRIORITY': 'debug', }, 'ESPLORA': { 'REST_API_URL': 'http://127.0.0.1:3000', @@ -128,6 +141,17 @@ const defaults: IConfig = { 'ENABLED': false, 'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db' }, + 'SOCKS5PROXY': { + 'ENABLED': false, + 'HOST': '127.0.0.1', + 'PORT': 9050, + 'USERNAME': '', + 'PASSWORD': '' + }, + "PRICE_DATA_SERVER": { + 'TOR_URL': 'http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices', + 'CLEARNET_URL': 'https://price.bisq.wiz.biz/getAllMarketPrices' + } }; class Config implements IConfig { @@ -140,6 +164,8 @@ class Config implements IConfig { SYSLOG: IConfig['SYSLOG']; STATISTICS: IConfig['STATISTICS']; BISQ: IConfig['BISQ']; + SOCKS5PROXY: IConfig['SOCKS5PROXY']; + PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER']; constructor() { const configs = this.merge(configFile, defaults); @@ -152,6 +178,8 @@ class Config implements IConfig { this.SYSLOG = configs.SYSLOG; this.STATISTICS = configs.STATISTICS; this.BISQ = configs.BISQ; + this.SOCKS5PROXY = configs.SOCKS5PROXY; + this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER; } merge = (...objects: object[]): IConfig => { diff --git a/backend/src/index.ts b/backend/src/index.ts index f78c5922b..557c269dd 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -319,7 +319,9 @@ class Server { if (Common.isLiquid()) { this.app .get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon) + .get(config.MEMPOOL.API_URL_PREFIX + 'assets/featured', routes.$getAllFeaturedLiquidAssets) .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon) + .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', routes.$getAssetGroup) ; } diff --git a/backend/src/logger.ts b/backend/src/logger.ts index 4e8c5ea11..43373e043 100644 --- a/backend/src/logger.ts +++ b/backend/src/logger.ts @@ -97,6 +97,9 @@ class Logger { syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${msg}`; this.syslog(syslogmsg); } + if (Logger.priorities[priority] > Logger.priorities[config.MEMPOOL.STDOUT_LOG_MIN_PRIORITY]) { + return; + } if (priority === 'warning') { priority = 'warn'; } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 5fb83d792..7dfcd3956 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -76,7 +76,8 @@ export interface TransactionStripped { vsize: number; value: number; } -export interface BlockExtended extends IEsploraApi.Block { + +export interface BlockExtension { medianFee?: number; feeRange?: number[]; reward?: number; @@ -84,6 +85,10 @@ export interface BlockExtended extends IEsploraApi.Block { matchRate?: number; } +export interface BlockExtended extends IEsploraApi.Block { + extras?: BlockExtension; +} + export interface TransactionMinerInfo { vin: VinStrippedToScriptsig[]; vout: VoutStrippedToScriptPubkey[]; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 947403e88..fd9549845 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -31,9 +31,18 @@ class BlocksRepository { )`; const params: any[] = [ - block.height, blockHash, block.timestamp, block.size, - block.weight, block.tx_count, coinbaseHex ? coinbaseHex : '', block.difficulty, - poolTag.id, 0, '[]', block.medianFee, + block.height, + blockHash, + block.timestamp, + block.size, + block.weight, + block.tx_count, + coinbaseHex ? coinbaseHex : '', + block.difficulty, + poolTag.id, + 0, + '[]', + block.extras ? block.extras.medianFee : 0, ]; await connection.query(query, params); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 044f9a3ac..8ae2f9609 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -21,6 +21,7 @@ import bitcoinClient from './api/bitcoin/bitcoin-client'; import elementsParser from './api/liquid/elements-parser'; import icons from './api/liquid/icons'; import miningStats from './api/mining'; +import axios from 'axios'; class Routes { constructor() {} @@ -855,6 +856,25 @@ class Routes { res.status(404).send('Asset icons not found'); } } + + public async $getAllFeaturedLiquidAssets(req: Request, res: Response) { + try { + const response = await axios.get('https://liquid.network/api/v1/assets/featured', { responseType: 'stream', timeout: 10000 }); + response.data.pipe(res); + } catch (e) { + res.status(500).end(); + } + } + + public async $getAssetGroup(req: Request, res: Response) { + try { + const response = await axios.get('https://liquid.network/api/v1/assets/group/' + parseInt(req.params.id, 10), + { responseType: 'stream', timeout: 10000 }); + response.data.pipe(res); + } catch (e) { + res.status(500).end(); + } + } } export default new Routes(); diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 85a3cb358..8b4cbea2e 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -10,7 +10,8 @@ "moduleResolution": "node", "typeRoots": [ "node_modules/@types" - ] + ], + "allowSyntheticDefaultImports": true }, "include": [ "src/**/*.ts" diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index 00dc31aad..3a0a0ec0b 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -14,7 +14,8 @@ "MEMPOOL_BLOCKS_AMOUNT": __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__, "PRICE_FEED_UPDATE_INTERVAL": __MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__, "USE_SECOND_NODE_FOR_MINFEE": __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__, - "EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__ + "EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__, + "STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__" }, "CORE_RPC": { "HOST": "__CORE_RPC_HOST__", @@ -58,5 +59,16 @@ "BISQ": { "ENABLED": __BISQ_ENABLED__, "DATA_PATH": "__BISQ_DATA_PATH__" + }, + "SOCKS5PROXY": { + "ENABLED": __SOCKS5PROXY_ENABLED__, + "HOST": "__SOCKS5PROXY_HOST__", + "PORT": "__SOCKS5PROXY_PORT__", + "USERNAME": "__SOCKS5PROXY_USERNAME__", + "PASSWORD": "__SOCKS5PROXY_PASSWORD__" + }, + "PRICE_DATA_SERVER": { + "TOR_URL": "__PRICE_DATA_SERVER_TOR_URL__", + "CLEARNET_URL": "__PRICE_DATA_SERVER_CLEARNET_URL__" } } diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 372be346e..019f16217 100644 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -14,9 +14,10 @@ __MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000} __MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8} __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8} __MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=1100} -__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=3600} +__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=600} __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false} -__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]} +__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[\"https://mempool.space/resources/pools.json\"]} +__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=debug} # CORE_RPC __CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1} @@ -61,6 +62,17 @@ __STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__=${STATISTICS_TX_PER_SECOND_SAMPLE_PER __BISQ_ENABLED__=${BISQ_ENABLED:=false} __BISQ_DATA_PATH__=${BISQ_DATA_PATH:=/bisq/statsnode-data/btc_mainnet/db} +# SOCKS5PROXY +__SOCKS5PROXY_ENABLED__=${SOCKS5PROXY_ENABLED:=false} +__SOCKS5PROXY_HOST__=${SOCKS5PROXY_HOST:=localhost} +__SOCKS5PROXY_PORT__=${SOCKS5PROXY_PORT:=9050} +__SOCKS5PROXY_USERNAME__=${SOCKS5PROXY_USERNAME:=""} +__SOCKS5PROXY_PASSWORD__=${SOCKS5PROXY_PASSWORD:=""} + +# PRICE_DATA_SERVER +__PRICE_DATA_SERVER_TOR_URL__=${PRICE_DATA_SERVER_TOR_URL:=http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices} +__PRICE_DATA_SERVER_CLEARNET_URL__=${PRICE_DATA_SERVER_CLEARNET_URL:=https://price.bisq.wiz.biz/getAllMarketPrices} + mkdir -p "${__MEMPOOL_CACHE_DIR__}" sed -i "s/__MEMPOOL_NETWORK__/${__MEMPOOL_NETWORK__}/g" mempool-config.json @@ -78,7 +90,8 @@ sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__} sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json sed -i "s/__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__/${__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__}/g" mempool-config.json sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json -sed -i "s/__MEMPOOL_EXTERNAL_ASSETS__/${__MEMPOOL_EXTERNAL_ASSETS__}/g" mempool-config.json +sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json +sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json @@ -115,4 +128,13 @@ sed -i "s/__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__/${__STATISTICS_TX_PER_SECON sed -i "s/__BISQ_ENABLED__/${__BISQ_ENABLED__}/g" mempool-config.json sed -i "s!__BISQ_DATA_PATH__!${__BISQ_DATA_PATH__}!g" mempool-config.json +sed -i "s/__SOCKS5PROXY_ENABLED__/${__SOCKS5PROXY_ENABLED__}/g" mempool-config.json +sed -i "s/__SOCKS5PROXY_HOST__/${__SOCKS5PROXY_HOST__}/g" mempool-config.json +sed -i "s/__SOCKS5PROXY_PORT__/${__SOCKS5PROXY_PORT__}/g" mempool-config.json +sed -i "s/__SOCKS5PROXY_USERNAME__/${__SOCKS5PROXY_USERNAME__}/g" mempool-config.json +sed -i "s/__SOCKS5PROXY_PASSWORD__/${__SOCKS5PROXY_PASSWORD__}/g" mempool-config.json + +sed -i "s!__PRICE_DATA_SERVER_TOR_URL__!${__PRICE_DATA_SERVER_TOR_URL__}!g" mempool-config.json +sed -i "s!__PRICE_DATA_SERVER_CLEARNET_URL__!${__PRICE_DATA_SERVER_CLEARNET_URL__}!g" mempool-config.json + node /backend/dist/index.js diff --git a/frontend/.gitignore b/frontend/.gitignore index 64b7777ea..789881ddd 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -53,6 +53,7 @@ src/resources/assets.minimal.json src/resources/assets-testnet.json src/resources/assets-testnet.minimal.json src/resources/pools.json +src/resources/mining-pools/* # environment config mempool-frontend-config.json diff --git a/frontend/angular.json b/frontend/angular.json index 054f72318..1d2fe3e6b 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -218,6 +218,10 @@ "proxyConfig": "proxy.conf.local.js", "verbose": true }, + "mixed": { + "proxyConfig": "proxy.conf.mixed.js", + "verbose": true + }, "staging": { "proxyConfig": "proxy.conf.js", "disableHostCheck": true, @@ -229,6 +233,12 @@ "disableHostCheck": true, "host": "0.0.0.0", "verbose": false + }, + "local-staging": { + "proxyConfig": "proxy.conf.staging.js", + "disableHostCheck": true, + "host": "0.0.0.0", + "verbose": false } } }, diff --git a/frontend/cypress/integration/liquid/liquid.spec.ts b/frontend/cypress/integration/liquid/liquid.spec.ts index af76314a1..d8d1c366d 100644 --- a/frontend/cypress/integration/liquid/liquid.spec.ts +++ b/frontend/cypress/integration/liquid/liquid.spec.ts @@ -115,17 +115,16 @@ describe('Liquid', () => { describe('assets', () => { it('shows the assets screen', () => { - cy.visit(`${basePath}`); - cy.get('#btn-assets'); + cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); - cy.get('table tr').should('have.length.at.least', 5); + cy.get('.featuredBox .card').should('have.length.at.least', 5); }); it('allows searching assets', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { - cy.get('table tr').should('have.length', 1); + cy.get('ngb-typeahead-window').should('have.length', 1); }); }); @@ -133,7 +132,7 @@ describe('Liquid', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); cy.get('.container-xl input').click().type('Liquid AUD').then(() => { - cy.get('table tr td:nth-of-type(1) a').click(); + cy.get('ngb-typeahead-window:nth-of-type(1) button').click(); }); }); }); @@ -197,7 +196,7 @@ describe('Liquid', () => { }); it('shows asset peg in/out and burn transactions', () => { - cy.visit(`${basePath}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`); + cy.visit(`${basePath}/assets/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`); cy.waitForSkeletonGone(); cy.get('#table-tx-vout tr').not('.assetBox'); cy.get('#table-tx-vin tr').not('.assetBox'); diff --git a/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts b/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts index eb75be773..90741539b 100644 --- a/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts +++ b/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts @@ -73,17 +73,11 @@ describe('Liquid Testnet', () => { }); describe('assets', () => { - it('shows the assets screen', () => { - cy.visit(`${basePath}/assets`); - cy.waitForSkeletonGone(); - cy.get('table tr').should('have.length.at.least', 5); - }); - it('allows searching assets', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { - cy.get('table tr').should('have.length', 1); + cy.get('ngb-typeahead-window').should('have.length', 1); }); }); @@ -91,7 +85,7 @@ describe('Liquid Testnet', () => { cy.visit(`${basePath}/assets`); cy.waitForSkeletonGone(); cy.get('.container-xl input').click().type('Liquid CAD').then(() => { - cy.get('table tr td:nth-of-type(1) a').click(); + cy.get('ngb-typeahead-window:nth-of-type(1) button').click(); }); }); }); @@ -150,7 +144,7 @@ describe('Liquid Testnet', () => { cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`); cy.get('#table-tx-vout tr:nth-child(2) .amount a').click().then(() => { cy.waitForSkeletonGone(); - cy.url().should('contain', '/asset/38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5'); + cy.url().should('contain', '/assets/asset/38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5'); }); }); @@ -162,7 +156,7 @@ describe('Liquid Testnet', () => { }); it('shows asset peg in/out and burn transactions', () => { - cy.visit(`${basePath}/asset/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`); + cy.visit(`${basePath}/assets/asset/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`); cy.waitForSkeletonGone(); cy.get('#table-tx-vout tr').not('.assetBox'); cy.get('#table-tx-vin tr').not('.assetBox'); diff --git a/frontend/package.json b/frontend/package.json index f00594803..b1723da19 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,9 +27,12 @@ "serve": "npm run generate-config && ng serve -c local", "serve:stg": "npm run generate-config && ng serve -c staging", "serve:local-prod": "npm run generate-config && ng serve -c local-prod", + "serve:local-staging": "npm run generate-config && ng serve -c local-staging", "start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local", "start:stg": "npm run generate-config && npm run sync-assets-dev && ng serve -c staging", "start:local-prod": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-prod", + "start:local-staging": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-staging", + "start:mixed": "npm run generate-config && npm run sync-assets-dev && ng serve -c mixed", "build": "npm run generate-config && ng build --configuration production --localize && npm run sync-assets && npm run build-mempool.js", "sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources", "sync-assets-dev": "node sync-assets.js dev", @@ -53,7 +56,10 @@ "cypress:run": "cypress run", "cypress:run:record": "cypress run --record", "cypress:open:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:open", - "cypress:run:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record" + "cypress:run:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record", + "cypress:open:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:open", + "cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record" + }, "dependencies": { "@angular-devkit/build-angular": "^13.1.2", diff --git a/frontend/proxy.conf.js b/frontend/proxy.conf.js index 4a0489c77..77a77bb5a 100644 --- a/frontend/proxy.conf.js +++ b/frontend/proxy.conf.js @@ -20,8 +20,8 @@ try { PROXY_CONFIG = [ { - context: ['*', - '/api/**', '!/api/v1/ws', + context: ['*', + '/api/**', '!/api/v1/ws', '!/bisq', '!/bisq/**', '!/bisq/', '!/liquid', '!/liquid/**', '!/liquid/', '!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/', @@ -65,7 +65,13 @@ PROXY_CONFIG = [ ws: true, secure: false, changeOrigin: true - } + }, + { + context: ['/resources/mining-pools/**'], + target: "https://mempool.space", + secure: false, + changeOrigin: true + } ]; if (configContent && configContent.BASE_MODULE == "liquid") { diff --git a/frontend/proxy.conf.local.js b/frontend/proxy.conf.local.js index ba2248e5a..5a6d1454e 100644 --- a/frontend/proxy.conf.local.js +++ b/frontend/proxy.conf.local.js @@ -1,17 +1,13 @@ - const fs = require('fs'); -let PROXY_CONFIG = require('./proxy.conf.js'); -const BACKEND_CONFIG_FILE_NAME = '../backend/mempool-config.json'; const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json'; -let backendConfigContent; -let frontendConfigContent; +let configContent; // Read frontend config try { const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME); - frontendConfigContent = JSON.parse(rawConfig); + configContent = JSON.parse(rawConfig); console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`); } catch (e) { console.log(e); @@ -22,51 +18,88 @@ try { } } -// Read backend config -try { - const rawConfig = fs.readFileSync(BACKEND_CONFIG_FILE_NAME); - backendConfigContent = JSON.parse(rawConfig); - console.log(`${BACKEND_CONFIG_FILE_NAME} file found, using provided config`); -} catch (e) { - console.log(e); - if (e.code !== 'ENOENT') { - throw new Error(e); - } else { - console.log(`${BACKEND_CONFIG_FILE_NAME} file not found, using default config`); +let PROXY_CONFIG = []; + +if (configContent && configContent.BASE_MODULE === 'liquid') { + PROXY_CONFIG.push(...[ + { + context: ['/liquid/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/liquid": "" + }, + }, + { + context: ['/liquid/api/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/liquid/api/": "/api/v1/" + }, + } + ]); +} + + +if (configContent && configContent.BASE_MODULE === 'bisq') { + PROXY_CONFIG.push(...[ + { + context: ['/bisq/api/v1/ws'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/bisq": "" + }, + }, + { + context: ['/bisq/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + }, + { + context: ['/bisq/api/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/bisq/api/": "/api/v1/bisq/" + }, + } + ]); +} + +PROXY_CONFIG.push(...[ + { + context: ['/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + }, + { + context: ['/api/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/api/": "/api/v1/" + }, } -} - -// Remove the "/api/**" entry from the default proxy config -let localDevContext = PROXY_CONFIG[0].context - -localDevContext.splice(PROXY_CONFIG[0].context.indexOf('/api/**'), 1); - -PROXY_CONFIG[0].context = localDevContext; - -// Change all targets to localhost -PROXY_CONFIG.map(conf => conf.target = "http://localhost:8999"); - -// Add rules for local backend -if (backendConfigContent) { - PROXY_CONFIG.push({ - context: ['/api/address/**', '/api/tx/**', '/api/block/**', '/api/blocks/**'], - target: `http://localhost:8999`, - secure: false, - changeOrigin: true, - proxyTimeout: 30000, - pathRewrite: { - "^/api/": "/api/v1/" - }, - }); - PROXY_CONFIG.push({ - context: ['/api/v1/**'], - target: `http://localhost:8999`, - secure: false, - changeOrigin: true, - proxyTimeout: 30000 - }); - -} +]); console.log(PROXY_CONFIG); diff --git a/frontend/proxy.conf.mixed.js b/frontend/proxy.conf.mixed.js new file mode 100644 index 000000000..6ee44a16f --- /dev/null +++ b/frontend/proxy.conf.mixed.js @@ -0,0 +1,99 @@ +const fs = require('fs'); + +const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json'; + +let configContent; + +// Read frontend config +try { + const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME); + configContent = JSON.parse(rawConfig); + console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`); +} catch (e) { + console.log(e); + if (e.code !== 'ENOENT') { + throw new Error(e); + } else { + console.log(`${FRONTEND_CONFIG_FILE_NAME} file not found, using default config`); + } +} + +let PROXY_CONFIG = []; + +if (configContent && configContent.BASE_MODULE === 'liquid') { + PROXY_CONFIG.push(...[ + { + context: ['/liquid/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/liquid": "" + }, + }, + { + context: ['/liquid/api/**'], + target: `https://liquid.network`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + }, + ]); +} + +if (configContent && configContent.BASE_MODULE === 'bisq') { + PROXY_CONFIG.push(...[ + { + context: ['/bisq/api/v1/ws'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/bisq": "" + }, + }, + { + context: ['/bisq/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + }, + { + context: ['/bisq/api/**'], + target: `http://localhost:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/bisq/api/": "/api/v1/bisq/" + }, + }, + ]); +} + +PROXY_CONFIG.push(...[ + { + context: ['/api/v1/**'], + target: `http://localhost:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + }, + { + context: ['/api/**'], + target: `https://mempool.space`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + } +]); + +console.log(PROXY_CONFIG); + +module.exports = PROXY_CONFIG; \ No newline at end of file diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js new file mode 100644 index 000000000..098edb619 --- /dev/null +++ b/frontend/proxy.conf.staging.js @@ -0,0 +1,11 @@ +const fs = require('fs'); + +let PROXY_CONFIG = require('./proxy.conf'); + +PROXY_CONFIG.forEach(entry => { + entry.target = entry.target.replace("mempool.space", "mempool.ninja"); + entry.target = entry.target.replace("liquid.network", "liquid.place"); + entry.target = entry.target.replace("bisq.markets", "bisq.ninja"); +}); + +module.exports = PROXY_CONFIG; diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 36a53781f..aaf545206 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -10,7 +10,7 @@ import { TelevisionComponent } from './components/television/television.componen import { StatisticsComponent } from './components/statistics/statistics.component'; import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; import { AssetComponent } from './components/asset/asset.component'; -import { AssetsComponent } from './assets/assets.component'; +import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component'; @@ -23,6 +23,9 @@ import { SponsorComponent } from './components/sponsor/sponsor.component'; import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component'; import { PushTransactionComponent } from './components/push-transaction/push-transaction.component'; import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.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'; let routes: Routes = [ { @@ -343,13 +346,31 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { path: 'address/:id', component: AddressComponent }, - { - path: 'asset/:id', - component: AssetComponent - }, { path: 'assets', - component: AssetsComponent, + component: AssetsNavComponent, + children: [ + { + path: 'featured', + component: AssetsFeaturedComponent, + }, + { + path: 'all', + component: AssetsComponent, + }, + { + path: 'asset/:id', + component: AssetComponent + }, + { + path: 'group/:id', + component: AssetGroupComponent + }, + { + path: '**', + redirectTo: 'featured' + } + ] }, { path: 'docs/api/:type', @@ -434,13 +455,27 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { path: 'address/:id', component: AddressComponent }, - { - path: 'asset/:id', - component: AssetComponent - }, { path: 'assets', - component: AssetsComponent, + component: AssetsNavComponent, + children: [ + { + path: 'all', + component: AssetsComponent, + }, + { + path: 'asset/:id', + component: AssetComponent + }, + { + path: 'group/:id', + component: AssetGroupComponent + }, + { + path: '**', + redirectTo: 'all' + } + ] }, { path: 'docs/api/:type', diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f9eae0666..97fc16204 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -40,7 +40,8 @@ import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph. import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component'; import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component'; import { AssetComponent } from './components/asset/asset.component'; -import { AssetsComponent } from './assets/assets.component'; +import { AssetsComponent } from './components/assets/assets.component'; +import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; import { MinerComponent } from './components/miner/miner.component'; import { SharedModule } from './shared/shared.module'; @@ -64,6 +65,8 @@ import { LanguageService } from './services/language.service'; import { SponsorComponent } from './components/sponsor/sponsor.component'; import { PushTransactionComponent } from './components/push-transaction/push-transaction.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component'; +import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component'; @NgModule({ declarations: [ @@ -110,6 +113,9 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; PushTransactionComponent, DocsComponent, ApiDocsNavComponent, + AssetsNavComponent, + AssetsFeaturedComponent, + AssetGroupComponent, ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), diff --git a/frontend/src/app/assets/assets.component.html b/frontend/src/app/assets/assets.component.html deleted file mode 100644 index c8962cd15..000000000 --- a/frontend/src/app/assets/assets.component.html +++ /dev/null @@ -1,71 +0,0 @@ -
-
-

Registered assets

-
-
- -
-
- -
- -
-
-
- - - - - - - - - - - - - - - - - -
NameTickerIssuer domainAsset ID
{{ asset.name }}{{ asset.ticker }}{{ asset.entity && asset.entity.domain }}{{ asset.asset_id | shortenString : 13 }}
- -
- - - -
- - - - - - - - - - - - - - - - - - -
NameTickerIssuer domainAsset ID
- -
- - -
- Error loading assets data. -
- {{ error.error }} -
-
- -
- -
diff --git a/frontend/src/app/assets/assets.component.spec.ts b/frontend/src/app/assets/assets.component.spec.ts deleted file mode 100644 index ed39b7122..000000000 --- a/frontend/src/app/assets/assets.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AssetsComponent } from './assets.component'; - -describe('AssetsComponent', () => { - let component: AssetsComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ AssetsComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(AssetsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/assets/assets.component.ts b/frontend/src/app/assets/assets.component.ts deleted file mode 100644 index 49c42d76e..000000000 --- a/frontend/src/app/assets/assets.component.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { AssetsService } from '../services/assets.service'; -import { environment } from 'src/environments/environment'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { distinctUntilChanged, map, filter, mergeMap, tap, take } from 'rxjs/operators'; -import { ActivatedRoute, Router } from '@angular/router'; -import { merge, combineLatest, Observable } from 'rxjs'; -import { AssetExtended } from '../interfaces/electrs.interface'; -import { SeoService } from '../services/seo.service'; -import { StateService } from '../services/state.service'; - -@Component({ - selector: 'app-assets', - templateUrl: './assets.component.html', - styleUrls: ['./assets.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AssetsComponent implements OnInit { - nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; - - assets: AssetExtended[]; - assetsCache: AssetExtended[]; - searchForm: FormGroup; - assets$: Observable; - - error: any; - - page = 1; - itemsPerPage: number; - contentSpace = window.innerHeight - (250 + 200); - fiveItemsPxSize = 250; - - constructor( - private assetsService: AssetsService, - private formBuilder: FormBuilder, - private route: ActivatedRoute, - private router: Router, - private seoService: SeoService, - private stateService: StateService, - ) { } - - ngOnInit() { - this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`); - this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); - - this.searchForm = this.formBuilder.group({ - searchText: [{ value: '', disabled: true }, Validators.required] - }); - - this.assets$ = combineLatest([ - this.assetsService.getAssetsJson$, - this.route.queryParams - ]) - .pipe( - take(1), - mergeMap(([assets, qp]) => { - this.assets = Object.values(assets); - if (this.stateService.network === 'liquid') { - // @ts-ignore - this.assets.push({ - name: 'Liquid Bitcoin', - ticker: 'L-BTC', - asset_id: this.nativeAssetId, - }); - } else if (this.stateService.network === 'liquidtestnet') { - // @ts-ignore - this.assets.push({ - name: 'Test Liquid Bitcoin', - ticker: 'tL-BTC', - asset_id: this.nativeAssetId, - }); - } - - this.assets = this.assets.sort((a: any, b: any) => a.name.localeCompare(b.name)); - this.assetsCache = this.assets; - this.searchForm.get('searchText').enable(); - - if (qp.search) { - this.searchForm.get('searchText').setValue(qp.search, { emitEvent: false }); - } - - return merge( - this.searchForm.get('searchText').valueChanges - .pipe( - distinctUntilChanged(), - tap((text) => { - this.page = 1; - this.searchTextChanged(text); - }) - ), - this.route.queryParams - .pipe( - filter((queryParams) => { - const newPage = parseInt(queryParams.page, 10); - if (newPage !== this.page || queryParams.search !== this.searchForm.get('searchText').value) { - return true; - } - return false; - }), - map((queryParams) => { - if (queryParams.page) { - const newPage = parseInt(queryParams.page, 10); - this.page = newPage; - } else { - this.page = 1; - } - if (this.searchForm.get('searchText').value !== (queryParams.search || '')) { - this.searchTextChanged(queryParams.search); - } - if (queryParams.search) { - this.searchForm.get('searchText').setValue(queryParams.search, { emitEvent: false }); - return queryParams.search; - } - return ''; - }) - ), - ); - }), - map((searchText) => { - const start = (this.page - 1) * this.itemsPerPage; - if (searchText.length ) { - const filteredAssets = this.assetsCache.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 - || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1); - this.assets = filteredAssets; - return filteredAssets.slice(start, this.itemsPerPage + start); - } else { - this.assets = this.assetsCache; - return this.assets.slice(start, this.itemsPerPage + start); - } - }) - ); - } - - pageChange(page: number) { - const queryParams = { page: page, search: this.searchForm.get('searchText').value }; - if (queryParams.search === '') { - queryParams.search = null; - } - if (queryParams.page === 1) { - queryParams.page = null; - } - this.page = -1; - this.router.navigate([], { - relativeTo: this.route, - queryParams: queryParams, - queryParamsHandling: 'merge', - }); - } - - searchTextChanged(text: string) { - const queryParams = { search: text, page: 1 }; - if (queryParams.search === '') { - queryParams.search = null; - } - if (queryParams.page === 1) { - queryParams.page = null; - } - this.router.navigate([], { - relativeTo: this.route, - queryParams: queryParams, - queryParamsHandling: 'merge', - }); - } - - trackByAsset(index: number, asset: any) { - return asset.asset_id; - } -} diff --git a/frontend/src/app/components/asset/asset.component.html b/frontend/src/app/components/asset/asset.component.html index 9723a45e5..b1728a0ff 100644 --- a/frontend/src/app/components/asset/asset.component.html +++ b/frontend/src/app/components/asset/asset.component.html @@ -2,7 +2,7 @@

Asset