Merge branch 'master' into refactor-docs-2
This commit is contained in:
		
						commit
						43e604507d
					
				
							
								
								
									
										19
									
								
								.github/workflows/cypress.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/cypress.yml
									
									
									
									
										vendored
									
									
								
							@ -1,7 +1,10 @@
 | 
				
			|||||||
name: Cypress Tests
 | 
					name: Cypress Tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on: [push, pull_request]
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - master
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  cypress:
 | 
					  cypress:
 | 
				
			||||||
    runs-on: ${{ matrix.os }}
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
@ -24,9 +27,10 @@ jobs:
 | 
				
			|||||||
      - name: ${{ matrix.browser }} browser tests (Mempool)
 | 
					      - name: ${{ matrix.browser }} browser tests (Mempool)
 | 
				
			||||||
        uses: cypress-io/github-action@v2
 | 
					        uses: cypress-io/github-action@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
 | 
					          tag: ${{ github.event_name }}
 | 
				
			||||||
          working-directory: frontend
 | 
					          working-directory: frontend
 | 
				
			||||||
          build: npm run config:defaults:mempool
 | 
					          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: 'http://localhost:4200'
 | 
				
			||||||
          wait-on-timeout: 120
 | 
					          wait-on-timeout: 120
 | 
				
			||||||
          record: true
 | 
					          record: true
 | 
				
			||||||
@ -39,6 +43,7 @@ jobs:
 | 
				
			|||||||
          browser: ${{ matrix.browser }}
 | 
					          browser: ${{ matrix.browser }}
 | 
				
			||||||
          ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
 | 
					          ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
 | 
					          COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
 | 
				
			||||||
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
 | 
					          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
 | 
				
			||||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
					          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
          CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
 | 
					          CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
 | 
				
			||||||
@ -47,9 +52,10 @@ jobs:
 | 
				
			|||||||
        uses: cypress-io/github-action@v2
 | 
					        uses: cypress-io/github-action@v2
 | 
				
			||||||
        if: always()
 | 
					        if: always()
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
 | 
					          tag: ${{ github.event_name }}
 | 
				
			||||||
          working-directory: frontend
 | 
					          working-directory: frontend
 | 
				
			||||||
          build: npm run config:defaults:liquid
 | 
					          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: 'http://localhost:4200'
 | 
				
			||||||
          wait-on-timeout: 120
 | 
					          wait-on-timeout: 120
 | 
				
			||||||
          record: true
 | 
					          record: true
 | 
				
			||||||
@ -61,6 +67,7 @@ jobs:
 | 
				
			|||||||
          browser: ${{ matrix.browser }}
 | 
					          browser: ${{ matrix.browser }}
 | 
				
			||||||
          ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
 | 
					          ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
 | 
					          COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
 | 
				
			||||||
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
 | 
					          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
 | 
				
			||||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
					          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
          CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
 | 
					          CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
 | 
				
			||||||
@ -69,9 +76,10 @@ jobs:
 | 
				
			|||||||
        uses: cypress-io/github-action@v2
 | 
					        uses: cypress-io/github-action@v2
 | 
				
			||||||
        if: always()
 | 
					        if: always()
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
 | 
					          tag: ${{ github.event_name }}
 | 
				
			||||||
          working-directory: frontend
 | 
					          working-directory: frontend
 | 
				
			||||||
          build: npm run config:defaults:bisq
 | 
					          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: 'http://localhost:4200'
 | 
				
			||||||
          wait-on-timeout: 120
 | 
					          wait-on-timeout: 120
 | 
				
			||||||
          record: true
 | 
					          record: true
 | 
				
			||||||
@ -81,6 +89,7 @@ jobs:
 | 
				
			|||||||
          browser: ${{ matrix.browser }}
 | 
					          browser: ${{ matrix.browser }}
 | 
				
			||||||
          ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
 | 
					          ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
 | 
					          COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
 | 
				
			||||||
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
 | 
					          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
 | 
				
			||||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
					          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
          CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
 | 
					          CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										41
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								README.md
									
									
									
									
									
								
							@ -1,4 +1,4 @@
 | 
				
			|||||||
# The Mempool Open Source Project™
 | 
					# The Mempool Open Source Project™ [](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.
 | 
					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,
 | 
					    "BLOCK_WEIGHT_UNITS": 4000000,
 | 
				
			||||||
    "INITIAL_BLOCKS_AMOUNT": 8,
 | 
					    "INITIAL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
					    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "PRICE_FEED_UPDATE_INTERVAL": 3600,
 | 
					    "PRICE_FEED_UPDATE_INTERVAL": 600,
 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
					    "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_PRICE_FEED_UPDATE_INTERVAL: ""
 | 
				
			||||||
      MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: ""
 | 
					      MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: ""
 | 
				
			||||||
      MEMPOOL_EXTERNAL_ASSETS: ""
 | 
					      MEMPOOL_EXTERNAL_ASSETS: ""
 | 
				
			||||||
 | 
					      MEMPOOL_STDOUT_LOG_MIN_PRIORITY: ""
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
JSON:
 | 
					JSON:
 | 
				
			||||||
@ -245,6 +247,39 @@ docker-compose overrides:
 | 
				
			|||||||
      BISQ_DATA_PATH: ""
 | 
					      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
 | 
					# Manual Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,11 +13,12 @@
 | 
				
			|||||||
    "INITIAL_BLOCKS_AMOUNT": 8,
 | 
					    "INITIAL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
					    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "INDEXING_BLOCKS_AMOUNT": 1100,
 | 
					    "INDEXING_BLOCKS_AMOUNT": 1100,
 | 
				
			||||||
    "PRICE_FEED_UPDATE_INTERVAL": 3600,
 | 
					    "PRICE_FEED_UPDATE_INTERVAL": 600,
 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
					    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
				
			||||||
    "EXTERNAL_ASSETS": [
 | 
					    "EXTERNAL_ASSETS": [
 | 
				
			||||||
      "https://mempool.space/resources/pools.json"
 | 
					      "https://mempool.space/resources/pools.json"
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
 | 
					    "STDOUT_LOG_MIN_PRIORITY": "debug"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "CORE_RPC": {
 | 
					  "CORE_RPC": {
 | 
				
			||||||
    "HOST": "127.0.0.1",
 | 
					    "HOST": "127.0.0.1",
 | 
				
			||||||
@ -61,5 +62,16 @@
 | 
				
			|||||||
  "BISQ": {
 | 
					  "BISQ": {
 | 
				
			||||||
    "ENABLED": false,
 | 
					    "ENABLED": false,
 | 
				
			||||||
    "DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
 | 
					    "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"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										161
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										161
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -19,6 +19,7 @@
 | 
				
			|||||||
        "locutus": "^2.0.12",
 | 
					        "locutus": "^2.0.12",
 | 
				
			||||||
        "mysql2": "2.3.3",
 | 
					        "mysql2": "2.3.3",
 | 
				
			||||||
        "node-worker-threads-pool": "^1.4.3",
 | 
					        "node-worker-threads-pool": "^1.4.3",
 | 
				
			||||||
 | 
					        "socks-proxy-agent": "^6.1.1",
 | 
				
			||||||
        "typescript": "4.4.4",
 | 
					        "typescript": "4.4.4",
 | 
				
			||||||
        "ws": "8.3.0"
 | 
					        "ws": "8.3.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -181,6 +182,38 @@
 | 
				
			|||||||
        "node": ">= 0.6"
 | 
					        "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": {
 | 
					    "node_modules/ansi-styles": {
 | 
				
			||||||
      "version": "3.2.1",
 | 
					      "version": "3.2.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
 | 
					      "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",
 | 
					      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
 | 
					      "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": {
 | 
					    "node_modules/ipaddr.js": {
 | 
				
			||||||
      "version": "1.9.1",
 | 
					      "version": "1.9.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 | 
				
			||||||
@ -1189,6 +1227,62 @@
 | 
				
			|||||||
        "sha.js": "bin.js"
 | 
					        "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": {
 | 
					    "node_modules/sprintf-js": {
 | 
				
			||||||
      "version": "1.0.3",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 | 
				
			||||||
@ -1538,6 +1632,29 @@
 | 
				
			|||||||
        "negotiator": "0.6.2"
 | 
					        "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": {
 | 
					    "ansi-styles": {
 | 
				
			||||||
      "version": "3.2.1",
 | 
					      "version": "3.2.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
 | 
					      "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",
 | 
					      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
 | 
					      "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": {
 | 
					    "ipaddr.js": {
 | 
				
			||||||
      "version": "1.9.1",
 | 
					      "version": "1.9.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 | 
				
			||||||
@ -2352,6 +2474,45 @@
 | 
				
			|||||||
        "safe-buffer": "^5.0.1"
 | 
					        "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": {
 | 
					    "sprintf-js": {
 | 
				
			||||||
      "version": "1.0.3",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,7 @@
 | 
				
			|||||||
    "locutus": "^2.0.12",
 | 
					    "locutus": "^2.0.12",
 | 
				
			||||||
    "mysql2": "2.3.3",
 | 
					    "mysql2": "2.3.3",
 | 
				
			||||||
    "node-worker-threads-pool": "^1.4.3",
 | 
					    "node-worker-threads-pool": "^1.4.3",
 | 
				
			||||||
 | 
					    "socks-proxy-agent": "^6.1.1",
 | 
				
			||||||
    "typescript": "4.4.4",
 | 
					    "typescript": "4.4.4",
 | 
				
			||||||
    "ws": "8.3.0"
 | 
					    "ws": "8.3.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
@ -301,8 +301,6 @@ class Blocks {
 | 
				
			|||||||
      if (memPool.isInSync()) {
 | 
					      if (memPool.isInSync()) {
 | 
				
			||||||
        diskCache.$saveCacheToDisk();
 | 
					        diskCache.$saveCacheToDisk();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,9 @@
 | 
				
			|||||||
import logger from '../logger';
 | 
					import logger from '../logger';
 | 
				
			||||||
import axios from 'axios';
 | 
					import axios, { AxiosResponse } from 'axios';
 | 
				
			||||||
import { IConversionRates } from '../mempool.interfaces';
 | 
					import { IConversionRates } from '../mempool.interfaces';
 | 
				
			||||||
import config from '../config';
 | 
					import config from '../config';
 | 
				
			||||||
 | 
					import backendInfo from './backend-info';
 | 
				
			||||||
 | 
					import { SocksProxyAgent } from 'socks-proxy-agent';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FiatConversion {
 | 
					class FiatConversion {
 | 
				
			||||||
  private conversionRates: IConversionRates = {
 | 
					  private conversionRates: IConversionRates = {
 | 
				
			||||||
@ -17,6 +19,11 @@ class FiatConversion {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  public startService() {
 | 
					  public startService() {
 | 
				
			||||||
    logger.info('Starting currency rates service');
 | 
					    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);
 | 
					    setInterval(this.updateCurrency.bind(this), 1000 * config.MEMPOOL.PRICE_FEED_UPDATE_INTERVAL);
 | 
				
			||||||
    this.updateCurrency();
 | 
					    this.updateCurrency();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -26,12 +33,43 @@ class FiatConversion {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async updateCurrency(): Promise<void> {
 | 
					  private async updateCurrency(): Promise<void> {
 | 
				
			||||||
 | 
					    const headers = { 'User-Agent': `mempool/v${backendInfo.getBackendInfo().version}` };
 | 
				
			||||||
 | 
					    let fiatConversionUrl: string;
 | 
				
			||||||
 | 
					    let response: AxiosResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    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');
 | 
					      const usd = response.data.data.find((item: any) => item.currencyCode === 'USD');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.conversionRates = {
 | 
					      this.conversionRates = {
 | 
				
			||||||
        'USD': usd.price,
 | 
					        'USD': usd.price,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      logger.debug(`USD Conversion Rate: ${usd.price}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.ratesChangedCallback) {
 | 
					      if (this.ratesChangedCallback) {
 | 
				
			||||||
        this.ratesChangedCallback(this.conversionRates);
 | 
					        this.ratesChangedCallback(this.conversionRates);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -168,7 +168,8 @@ class Mempool {
 | 
				
			|||||||
    const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx));
 | 
					    const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx));
 | 
				
			||||||
    this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6);
 | 
					    this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!this.inSync && transactions.length === Object.keys(this.mempoolCache).length) {
 | 
					    const syncedThreshold = 0.99; // If we synced 99% of the mempool tx count, consider we're synced
 | 
				
			||||||
 | 
					    if (!this.inSync && Object.keys(this.mempoolCache).length >= transactions.length * syncedThreshold) {
 | 
				
			||||||
      this.inSync = true;
 | 
					      this.inSync = true;
 | 
				
			||||||
      logger.notice('The mempool is now in sync!');
 | 
					      logger.notice('The mempool is now in sync!');
 | 
				
			||||||
      loadingIndicators.setProgress('mempool', 100);
 | 
					      loadingIndicators.setProgress('mempool', 100);
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ interface IConfig {
 | 
				
			|||||||
    PRICE_FEED_UPDATE_INTERVAL: number;
 | 
					    PRICE_FEED_UPDATE_INTERVAL: number;
 | 
				
			||||||
    USE_SECOND_NODE_FOR_MINFEE: boolean;
 | 
					    USE_SECOND_NODE_FOR_MINFEE: boolean;
 | 
				
			||||||
    EXTERNAL_ASSETS: string[];
 | 
					    EXTERNAL_ASSETS: string[];
 | 
				
			||||||
 | 
					    STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  ESPLORA: {
 | 
					  ESPLORA: {
 | 
				
			||||||
    REST_API_URL: string;
 | 
					    REST_API_URL: string;
 | 
				
			||||||
@ -51,7 +52,7 @@ interface IConfig {
 | 
				
			|||||||
    ENABLED: boolean;
 | 
					    ENABLED: boolean;
 | 
				
			||||||
    HOST: string;
 | 
					    HOST: string;
 | 
				
			||||||
    PORT: number;
 | 
					    PORT: number;
 | 
				
			||||||
    MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' |'warn' | 'notice' | 'info' | 'debug';
 | 
					    MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
 | 
				
			||||||
    FACILITY: string;
 | 
					    FACILITY: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  STATISTICS: {
 | 
					  STATISTICS: {
 | 
				
			||||||
@ -62,6 +63,17 @@ interface IConfig {
 | 
				
			|||||||
    ENABLED: boolean;
 | 
					    ENABLED: boolean;
 | 
				
			||||||
    DATA_PATH: string;
 | 
					    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 = {
 | 
					const defaults: IConfig = {
 | 
				
			||||||
@ -79,11 +91,12 @@ const defaults: IConfig = {
 | 
				
			|||||||
    'INITIAL_BLOCKS_AMOUNT': 8,
 | 
					    'INITIAL_BLOCKS_AMOUNT': 8,
 | 
				
			||||||
    'MEMPOOL_BLOCKS_AMOUNT': 8,
 | 
					    'MEMPOOL_BLOCKS_AMOUNT': 8,
 | 
				
			||||||
    'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks
 | 
					    '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,
 | 
					    'USE_SECOND_NODE_FOR_MINFEE': false,
 | 
				
			||||||
    'EXTERNAL_ASSETS': [
 | 
					    'EXTERNAL_ASSETS': [
 | 
				
			||||||
      'https://mempool.space/resources/pools.json'
 | 
					      'https://mempool.space/resources/pools.json'
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
 | 
					    'STDOUT_LOG_MIN_PRIORITY': 'debug',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  'ESPLORA': {
 | 
					  'ESPLORA': {
 | 
				
			||||||
    'REST_API_URL': 'http://127.0.0.1:3000',
 | 
					    'REST_API_URL': 'http://127.0.0.1:3000',
 | 
				
			||||||
@ -128,6 +141,17 @@ const defaults: IConfig = {
 | 
				
			|||||||
    'ENABLED': false,
 | 
					    'ENABLED': false,
 | 
				
			||||||
    'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db'
 | 
					    '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 {
 | 
					class Config implements IConfig {
 | 
				
			||||||
@ -140,6 +164,8 @@ class Config implements IConfig {
 | 
				
			|||||||
  SYSLOG: IConfig['SYSLOG'];
 | 
					  SYSLOG: IConfig['SYSLOG'];
 | 
				
			||||||
  STATISTICS: IConfig['STATISTICS'];
 | 
					  STATISTICS: IConfig['STATISTICS'];
 | 
				
			||||||
  BISQ: IConfig['BISQ'];
 | 
					  BISQ: IConfig['BISQ'];
 | 
				
			||||||
 | 
					  SOCKS5PROXY: IConfig['SOCKS5PROXY'];
 | 
				
			||||||
 | 
					  PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    const configs = this.merge(configFile, defaults);
 | 
					    const configs = this.merge(configFile, defaults);
 | 
				
			||||||
@ -152,6 +178,8 @@ class Config implements IConfig {
 | 
				
			|||||||
    this.SYSLOG = configs.SYSLOG;
 | 
					    this.SYSLOG = configs.SYSLOG;
 | 
				
			||||||
    this.STATISTICS = configs.STATISTICS;
 | 
					    this.STATISTICS = configs.STATISTICS;
 | 
				
			||||||
    this.BISQ = configs.BISQ;
 | 
					    this.BISQ = configs.BISQ;
 | 
				
			||||||
 | 
					    this.SOCKS5PROXY = configs.SOCKS5PROXY;
 | 
				
			||||||
 | 
					    this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  merge = (...objects: object[]): IConfig => {
 | 
					  merge = (...objects: object[]): IConfig => {
 | 
				
			||||||
 | 
				
			|||||||
@ -319,7 +319,9 @@ class Server {
 | 
				
			|||||||
    if (Common.isLiquid()) {
 | 
					    if (Common.isLiquid()) {
 | 
				
			||||||
      this.app
 | 
					      this.app
 | 
				
			||||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
 | 
					        .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 + 'asset/:assetId/icon', routes.getLiquidIcon)
 | 
				
			||||||
 | 
					        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', routes.$getAssetGroup)
 | 
				
			||||||
      ;
 | 
					      ;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -97,6 +97,9 @@ class Logger {
 | 
				
			|||||||
      syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${msg}`;
 | 
					      syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${msg}`;
 | 
				
			||||||
      this.syslog(syslogmsg);
 | 
					      this.syslog(syslogmsg);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (Logger.priorities[priority] > Logger.priorities[config.MEMPOOL.STDOUT_LOG_MIN_PRIORITY]) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (priority === 'warning') {
 | 
					    if (priority === 'warning') {
 | 
				
			||||||
      priority = 'warn';
 | 
					      priority = 'warn';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -46,8 +46,12 @@ class BlocksRepository {
 | 
				
			|||||||
      ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await connection.query(query, params);
 | 
					      await connection.query(query, params);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
      logger.err('$saveBlockInDatabase() error' + (e instanceof Error ? e.message : e));
 | 
					      if (e.errno === 1062) { // ER_DUP_ENTRY
 | 
				
			||||||
 | 
					        logger.debug(`$saveBlockInDatabase() - Block ${block.height} has already been indexed, ignoring`);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        logger.err('$saveBlockInDatabase() error' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connection.release();
 | 
					    connection.release();
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@ import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
				
			|||||||
import elementsParser from './api/liquid/elements-parser';
 | 
					import elementsParser from './api/liquid/elements-parser';
 | 
				
			||||||
import icons from './api/liquid/icons';
 | 
					import icons from './api/liquid/icons';
 | 
				
			||||||
import miningStats from './api/mining';
 | 
					import miningStats from './api/mining';
 | 
				
			||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Routes {
 | 
					class Routes {
 | 
				
			||||||
  constructor() {}
 | 
					  constructor() {}
 | 
				
			||||||
@ -855,6 +856,25 @@ class Routes {
 | 
				
			|||||||
      res.status(404).send('Asset icons not found');
 | 
					      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();
 | 
					export default new Routes();
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,8 @@
 | 
				
			|||||||
    "moduleResolution": "node",
 | 
					    "moduleResolution": "node",
 | 
				
			||||||
    "typeRoots": [
 | 
					    "typeRoots": [
 | 
				
			||||||
      "node_modules/@types"
 | 
					      "node_modules/@types"
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
 | 
					    "allowSyntheticDefaultImports": true
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "include": [
 | 
					  "include": [
 | 
				
			||||||
    "src/**/*.ts"
 | 
					    "src/**/*.ts"
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,8 @@
 | 
				
			|||||||
    "MEMPOOL_BLOCKS_AMOUNT": __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__,
 | 
					    "MEMPOOL_BLOCKS_AMOUNT": __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__,
 | 
				
			||||||
    "PRICE_FEED_UPDATE_INTERVAL": __MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__,
 | 
					    "PRICE_FEED_UPDATE_INTERVAL": __MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__,
 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__,
 | 
					    "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": {
 | 
					  "CORE_RPC": {
 | 
				
			||||||
    "HOST": "__CORE_RPC_HOST__",
 | 
					    "HOST": "__CORE_RPC_HOST__",
 | 
				
			||||||
@ -58,5 +59,16 @@
 | 
				
			|||||||
  "BISQ": {
 | 
					  "BISQ": {
 | 
				
			||||||
    "ENABLED": __BISQ_ENABLED__,
 | 
					    "ENABLED": __BISQ_ENABLED__,
 | 
				
			||||||
    "DATA_PATH": "__BISQ_DATA_PATH__"
 | 
					    "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__"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,9 +14,10 @@ __MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
 | 
				
			|||||||
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
 | 
					__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
 | 
				
			||||||
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
 | 
					__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
 | 
				
			||||||
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=1100}
 | 
					__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_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
 | 
				
			||||||
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
 | 
					__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_ENABLED__=${BISQ_ENABLED:=false}
 | 
				
			||||||
__BISQ_DATA_PATH__=${BISQ_DATA_PATH:=/bisq/statsnode-data/btc_mainnet/db}
 | 
					__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__}"
 | 
					mkdir -p "${__MEMPOOL_CACHE_DIR__}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sed -i "s/__MEMPOOL_NETWORK__/${__MEMPOOL_NETWORK__}/g" mempool-config.json
 | 
					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_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_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_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_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
 | 
				
			||||||
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/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_ENABLED__/${__BISQ_ENABLED__}/g" mempool-config.json
 | 
				
			||||||
sed -i "s!__BISQ_DATA_PATH__!${__BISQ_DATA_PATH__}!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
 | 
					node /backend/dist/index.js
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
								
							@ -53,6 +53,7 @@ src/resources/assets.minimal.json
 | 
				
			|||||||
src/resources/assets-testnet.json
 | 
					src/resources/assets-testnet.json
 | 
				
			||||||
src/resources/assets-testnet.minimal.json
 | 
					src/resources/assets-testnet.minimal.json
 | 
				
			||||||
src/resources/pools.json
 | 
					src/resources/pools.json
 | 
				
			||||||
 | 
					src/resources/mining-pools/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# environment config
 | 
					# environment config
 | 
				
			||||||
mempool-frontend-config.json
 | 
					mempool-frontend-config.json
 | 
				
			||||||
 | 
				
			|||||||
@ -233,6 +233,12 @@
 | 
				
			|||||||
              "disableHostCheck": true,
 | 
					              "disableHostCheck": true,
 | 
				
			||||||
              "host": "0.0.0.0",
 | 
					              "host": "0.0.0.0",
 | 
				
			||||||
              "verbose": false
 | 
					              "verbose": false
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "local-staging": {
 | 
				
			||||||
 | 
					              "proxyConfig": "proxy.conf.staging.js",
 | 
				
			||||||
 | 
					              "disableHostCheck": true,
 | 
				
			||||||
 | 
					              "host": "0.0.0.0",
 | 
				
			||||||
 | 
					              "verbose": false
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
				
			|||||||
@ -115,17 +115,16 @@ describe('Liquid', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    describe('assets', () => {
 | 
					    describe('assets', () => {
 | 
				
			||||||
      it('shows the assets screen', () => {
 | 
					      it('shows the assets screen', () => {
 | 
				
			||||||
        cy.visit(`${basePath}`);
 | 
					        cy.visit(`${basePath}/assets`);
 | 
				
			||||||
        cy.get('#btn-assets');
 | 
					 | 
				
			||||||
        cy.waitForSkeletonGone();
 | 
					        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', () => {
 | 
					      it('allows searching assets', () => {
 | 
				
			||||||
        cy.visit(`${basePath}/assets`);
 | 
					        cy.visit(`${basePath}/assets`);
 | 
				
			||||||
        cy.waitForSkeletonGone();
 | 
					        cy.waitForSkeletonGone();
 | 
				
			||||||
        cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
 | 
					        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.visit(`${basePath}/assets`);
 | 
				
			||||||
        cy.waitForSkeletonGone();
 | 
					        cy.waitForSkeletonGone();
 | 
				
			||||||
        cy.get('.container-xl input').click().type('Liquid AUD').then(() => {
 | 
					        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', () => {
 | 
					      it('shows asset peg in/out and burn transactions', () => {
 | 
				
			||||||
        cy.visit(`${basePath}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
 | 
					        cy.visit(`${basePath}/assets/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
 | 
				
			||||||
        cy.waitForSkeletonGone();
 | 
					        cy.waitForSkeletonGone();
 | 
				
			||||||
        cy.get('#table-tx-vout tr').not('.assetBox');
 | 
					        cy.get('#table-tx-vout tr').not('.assetBox');
 | 
				
			||||||
        cy.get('#table-tx-vin tr').not('.assetBox');
 | 
					        cy.get('#table-tx-vin tr').not('.assetBox');
 | 
				
			||||||
 | 
				
			|||||||
@ -73,17 +73,11 @@ describe('Liquid Testnet', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    describe('assets', () => {
 | 
					    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', () => {
 | 
					      it('allows searching assets', () => {
 | 
				
			||||||
        cy.visit(`${basePath}/assets`);
 | 
					        cy.visit(`${basePath}/assets`);
 | 
				
			||||||
        cy.waitForSkeletonGone();
 | 
					        cy.waitForSkeletonGone();
 | 
				
			||||||
        cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
 | 
					        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.visit(`${basePath}/assets`);
 | 
				
			||||||
        cy.waitForSkeletonGone();
 | 
					        cy.waitForSkeletonGone();
 | 
				
			||||||
        cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
 | 
					        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.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.get('#table-tx-vout tr:nth-child(2) .amount a').click().then(() => {
 | 
				
			||||||
          cy.waitForSkeletonGone();
 | 
					          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', () => {
 | 
					      it('shows asset peg in/out and burn transactions', () => {
 | 
				
			||||||
        cy.visit(`${basePath}/asset/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`);
 | 
					        cy.visit(`${basePath}/assets/asset/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`);
 | 
				
			||||||
        cy.waitForSkeletonGone();
 | 
					        cy.waitForSkeletonGone();
 | 
				
			||||||
        cy.get('#table-tx-vout tr').not('.assetBox');
 | 
					        cy.get('#table-tx-vout tr').not('.assetBox');
 | 
				
			||||||
        cy.get('#table-tx-vin tr').not('.assetBox');
 | 
					        cy.get('#table-tx-vin tr').not('.assetBox');
 | 
				
			||||||
 | 
				
			|||||||
@ -27,9 +27,11 @@
 | 
				
			|||||||
    "serve": "npm run generate-config && ng serve -c local",
 | 
					    "serve": "npm run generate-config && ng serve -c local",
 | 
				
			||||||
    "serve:stg": "npm run generate-config && ng serve -c staging",
 | 
					    "serve:stg": "npm run generate-config && ng serve -c staging",
 | 
				
			||||||
    "serve:local-prod": "npm run generate-config && ng serve -c local-prod",
 | 
					    "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": "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: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-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",
 | 
					    "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",
 | 
					    "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": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
 | 
				
			||||||
@ -54,7 +56,10 @@
 | 
				
			|||||||
    "cypress:run": "cypress run",
 | 
					    "cypress:run": "cypress run",
 | 
				
			||||||
    "cypress:run:record": "cypress run --record",
 | 
					    "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: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": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@angular-devkit/build-angular": "^13.1.2",
 | 
					    "@angular-devkit/build-angular": "^13.1.2",
 | 
				
			||||||
 | 
				
			|||||||
@ -20,8 +20,8 @@ try {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
PROXY_CONFIG = [
 | 
					PROXY_CONFIG = [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        context: ['*', 
 | 
					        context: ['*',
 | 
				
			||||||
        '/api/**', '!/api/v1/ws', 
 | 
					        '/api/**', '!/api/v1/ws',
 | 
				
			||||||
        '!/bisq', '!/bisq/**', '!/bisq/',
 | 
					        '!/bisq', '!/bisq/**', '!/bisq/',
 | 
				
			||||||
        '!/liquid', '!/liquid/**', '!/liquid/',
 | 
					        '!/liquid', '!/liquid/**', '!/liquid/',
 | 
				
			||||||
        '!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
 | 
					        '!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
 | 
				
			||||||
@ -65,7 +65,13 @@ PROXY_CONFIG = [
 | 
				
			|||||||
        ws: true,
 | 
					        ws: true,
 | 
				
			||||||
        secure: false,
 | 
					        secure: false,
 | 
				
			||||||
        changeOrigin: true
 | 
					        changeOrigin: true
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      context: ['/resources/mining-pools/**'],
 | 
				
			||||||
 | 
					      target: "https://mempool.space",
 | 
				
			||||||
 | 
					      secure: false,
 | 
				
			||||||
 | 
					      changeOrigin: true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (configContent && configContent.BASE_MODULE == "liquid") {
 | 
					if (configContent && configContent.BASE_MODULE == "liquid") {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								frontend/proxy.conf.staging.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								frontend/proxy.conf.staging.js
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
				
			||||||
@ -10,7 +10,7 @@ import { TelevisionComponent } from './components/television/television.componen
 | 
				
			|||||||
import { StatisticsComponent } from './components/statistics/statistics.component';
 | 
					import { StatisticsComponent } from './components/statistics/statistics.component';
 | 
				
			||||||
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
 | 
					import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
 | 
				
			||||||
import { AssetComponent } from './components/asset/asset.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 { StatusViewComponent } from './components/status-view/status-view.component';
 | 
				
			||||||
import { DashboardComponent } from './dashboard/dashboard.component';
 | 
					import { DashboardComponent } from './dashboard/dashboard.component';
 | 
				
			||||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.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 { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
 | 
				
			||||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
 | 
					import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
 | 
				
			||||||
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.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 = [
 | 
					let routes: Routes = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@ -343,13 +346,31 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
 | 
				
			|||||||
        path: 'address/:id',
 | 
					        path: 'address/:id',
 | 
				
			||||||
        component: AddressComponent
 | 
					        component: AddressComponent
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        path: 'asset/:id',
 | 
					 | 
				
			||||||
        component: AssetComponent
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        path: 'assets',
 | 
					        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',
 | 
					        path: 'docs/api/:type',
 | 
				
			||||||
@ -434,13 +455,27 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
 | 
				
			|||||||
            path: 'address/:id',
 | 
					            path: 'address/:id',
 | 
				
			||||||
            component: AddressComponent
 | 
					            component: AddressComponent
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            path: 'asset/:id',
 | 
					 | 
				
			||||||
            component: AssetComponent
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            path: 'assets',
 | 
					            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',
 | 
					            path: 'docs/api/:type',
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,8 @@ import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.
 | 
				
			|||||||
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
 | 
					import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
 | 
				
			||||||
import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component';
 | 
					import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component';
 | 
				
			||||||
import { AssetComponent } from './components/asset/asset.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 { StatusViewComponent } from './components/status-view/status-view.component';
 | 
				
			||||||
import { MinerComponent } from './components/miner/miner.component';
 | 
					import { MinerComponent } from './components/miner/miner.component';
 | 
				
			||||||
import { SharedModule } from './shared/shared.module';
 | 
					import { SharedModule } from './shared/shared.module';
 | 
				
			||||||
@ -64,6 +65,8 @@ import { LanguageService } from './services/language.service';
 | 
				
			|||||||
import { SponsorComponent } from './components/sponsor/sponsor.component';
 | 
					import { SponsorComponent } from './components/sponsor/sponsor.component';
 | 
				
			||||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
 | 
					import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
 | 
				
			||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 | 
					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({
 | 
					@NgModule({
 | 
				
			||||||
  declarations: [
 | 
					  declarations: [
 | 
				
			||||||
@ -110,6 +113,9 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 | 
				
			|||||||
    PushTransactionComponent,
 | 
					    PushTransactionComponent,
 | 
				
			||||||
    DocsComponent,
 | 
					    DocsComponent,
 | 
				
			||||||
    ApiDocsNavComponent,
 | 
					    ApiDocsNavComponent,
 | 
				
			||||||
 | 
					    AssetsNavComponent,
 | 
				
			||||||
 | 
					    AssetsFeaturedComponent,
 | 
				
			||||||
 | 
					    AssetGroupComponent,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
 | 
					    BrowserModule.withServerTransition({ appId: 'serverApp' }),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,71 +0,0 @@
 | 
				
			|||||||
<div class="container-xl">
 | 
					 | 
				
			||||||
  <div class="title-asset">
 | 
					 | 
				
			||||||
    <h1 i18n="Registered assets page header">Registered assets</h1>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
  <div class="clearfix"></div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <form [formGroup]="searchForm" class="form-inline">
 | 
					 | 
				
			||||||
    <div class="input-group mb-2">
 | 
					 | 
				
			||||||
      <input style="width: 250px;" formControlName="searchText" type="text" class="form-control" i18n-placeholder="Search Assets Placeholder Text" placeholder="Search asset">
 | 
					 | 
				
			||||||
      <div class="input-group-append">
 | 
					 | 
				
			||||||
        <button [disabled]="!searchForm.get('searchText')?.value.length" class="btn btn-secondary" type="button" (click)="searchForm.get('searchText')?.setValue('');" autocomplete="off" i18n="Search Clear Button">Clear</button>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </form>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <ng-container *ngIf="(assets$ | async) as filteredAssets; else isLoading">
 | 
					 | 
				
			||||||
    <table class="table table-borderless table-striped">
 | 
					 | 
				
			||||||
      <thead>
 | 
					 | 
				
			||||||
        <th class="td-name" i18n="Asset name header">Name</th>
 | 
					 | 
				
			||||||
        <th i18n="Asset ticker header">Ticker</th>
 | 
					 | 
				
			||||||
        <th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
					 | 
				
			||||||
        <th i18n="Asset ID header">Asset ID</th>
 | 
					 | 
				
			||||||
      </thead>
 | 
					 | 
				
			||||||
      <tbody>
 | 
					 | 
				
			||||||
        <tr *ngFor="let asset of filteredAssets; trackBy: trackByAsset">
 | 
					 | 
				
			||||||
          <td class="td-name"><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a></td>
 | 
					 | 
				
			||||||
          <td>{{ asset.ticker }}</td>
 | 
					 | 
				
			||||||
          <td class="d-none d-md-block">{{ asset.entity && asset.entity.domain }}</td>
 | 
					 | 
				
			||||||
          <td><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
 | 
					 | 
				
			||||||
        </tr>
 | 
					 | 
				
			||||||
      </tbody>
 | 
					 | 
				
			||||||
    </table>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <ngb-pagination [collectionSize]="assets.length" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="5" [boundaryLinks]="true"></ngb-pagination>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  </ng-container>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <ng-template #isLoading>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <table class="table table-borderless table-striped">
 | 
					 | 
				
			||||||
      <thead>
 | 
					 | 
				
			||||||
        <th i18n="Asset name header">Name</th>
 | 
					 | 
				
			||||||
        <th i18n="Asset ticker header">Ticker</th>
 | 
					 | 
				
			||||||
        <th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
					 | 
				
			||||||
        <th i18n="Asset ID header">Asset ID</th>
 | 
					 | 
				
			||||||
      </thead>
 | 
					 | 
				
			||||||
      <tbody>
 | 
					 | 
				
			||||||
        <tr *ngFor="let dummy of [0,0,0,0,0,0,0,0,0,0]">
 | 
					 | 
				
			||||||
          <td><span class="skeleton-loader"></span></td>
 | 
					 | 
				
			||||||
          <td><span class="skeleton-loader"></span></td>
 | 
					 | 
				
			||||||
          <td class="d-none d-md-block"><span class="skeleton-loader"></span></td>
 | 
					 | 
				
			||||||
          <td><span class="skeleton-loader"></span></td>
 | 
					 | 
				
			||||||
        </tr>
 | 
					 | 
				
			||||||
      </tbody>
 | 
					 | 
				
			||||||
    </table>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  </ng-template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <ng-template [ngIf]="error">
 | 
					 | 
				
			||||||
    <div class="text-center">
 | 
					 | 
				
			||||||
      <ng-container i18n="Asset data load error">Error loading assets data.</ng-container>
 | 
					 | 
				
			||||||
      <br>
 | 
					 | 
				
			||||||
      <i>{{ error.error }}</i>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </ng-template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<br>
 | 
					 | 
				
			||||||
@ -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<AssetsComponent>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeEach(async(() => {
 | 
					 | 
				
			||||||
    TestBed.configureTestingModule({
 | 
					 | 
				
			||||||
      declarations: [ AssetsComponent ]
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .compileComponents();
 | 
					 | 
				
			||||||
  }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeEach(() => {
 | 
					 | 
				
			||||||
    fixture = TestBed.createComponent(AssetsComponent);
 | 
					 | 
				
			||||||
    component = fixture.componentInstance;
 | 
					 | 
				
			||||||
    fixture.detectChanges();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should create', () => {
 | 
					 | 
				
			||||||
    expect(component).toBeTruthy();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@ -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<AssetExtended[]>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  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;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
  <div class="title-asset">
 | 
					  <div class="title-asset">
 | 
				
			||||||
    <h1 i18n="asset|Liquid Asset page title">Asset</h1>
 | 
					    <h1 i18n="asset|Liquid Asset page title">Asset</h1>
 | 
				
			||||||
    <div class="tx-link">
 | 
					    <div class="tx-link">
 | 
				
			||||||
      <a [routerLink]="['/asset/' | relativeUrl, assetString]">
 | 
					      <a [routerLink]="['/assets/asset/' | relativeUrl, assetString]">
 | 
				
			||||||
        <span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
 | 
					        <span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
 | 
				
			||||||
        <span class="d-none d-lg-inline">{{ assetString }}</span>
 | 
					        <span class="d-none d-lg-inline">{{ assetString }}</span>
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
@ -20,7 +20,7 @@
 | 
				
			|||||||
          <table class="table table-borderless table-striped">
 | 
					          <table class="table table-borderless table-striped">
 | 
				
			||||||
            <tbody>
 | 
					            <tbody>
 | 
				
			||||||
              <tr>
 | 
					              <tr>
 | 
				
			||||||
                <td i18n="asset.name|Liquid Asset name">Name</td>
 | 
					                <td i18n="Asset name header">Name</td>
 | 
				
			||||||
                <td class="assetName">{{ assetContract[2] }} ({{ assetContract[1] }})</td>
 | 
					                <td class="assetName">{{ assetContract[2] }} ({{ assetContract[1] }})</td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
              <tr>
 | 
					              <tr>
 | 
				
			||||||
 | 
				
			|||||||
@ -63,6 +63,7 @@ export class AssetComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
      .pipe(
 | 
					      .pipe(
 | 
				
			||||||
        switchMap((params: ParamMap) => {
 | 
					        switchMap((params: ParamMap) => {
 | 
				
			||||||
          this.error = undefined;
 | 
					          this.error = undefined;
 | 
				
			||||||
 | 
					          this.imageError = false;
 | 
				
			||||||
          this.isLoadingAsset = true;
 | 
					          this.isLoadingAsset = true;
 | 
				
			||||||
          this.loadedConfirmedTxCount = 0;
 | 
					          this.loadedConfirmedTxCount = 0;
 | 
				
			||||||
          this.asset = null;
 | 
					          this.asset = null;
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					<div *ngIf="group$ | async as group; else loading">
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  <div class="main-title">
 | 
				
			||||||
 | 
					    <h2>{{ group.group.name }}</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="sub-title" i18n>Group of {{ group.group.assets.length | number }} assets</div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="clearfix"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="featuredBox">
 | 
				
			||||||
 | 
					    <div *ngFor="let asset of group.assets">
 | 
				
			||||||
 | 
					      <div class="card">
 | 
				
			||||||
 | 
					        <a [routerLink]="['/assets/asset' | relativeUrl, asset.asset_id]">
 | 
				
			||||||
 | 
					          <img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + asset.asset_id + '/icon'">
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <div class="title">
 | 
				
			||||||
 | 
					          <a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="ticker">{{ asset.ticker }}</div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ng-template #loading>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <div class="text-center loadingGraphs">
 | 
				
			||||||
 | 
					    <div class="spinner-border text-light"></div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					.image {
 | 
				
			||||||
 | 
					  width: 150px;
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-title {
 | 
				
			||||||
 | 
					  float: left
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sub-title {
 | 
				
			||||||
 | 
					  color: grey;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.featuredBox {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-flow: row wrap;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  gap: 27px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card {
 | 
				
			||||||
 | 
					  background-color: #1d1f31;
 | 
				
			||||||
 | 
					  width: 200px;
 | 
				
			||||||
 | 
					  height: 200px;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  @media (max-width: 767.98px) {
 | 
				
			||||||
 | 
					    width: 150px;
 | 
				
			||||||
 | 
					    height: 150px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.title {
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					  font-weight: bold;
 | 
				
			||||||
 | 
					  margin-top: 10px;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sub-title {
 | 
				
			||||||
 | 
					  color: grey;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.assetIcon {
 | 
				
			||||||
 | 
					  width: 100px;
 | 
				
			||||||
 | 
					  height: 100px;
 | 
				
			||||||
 | 
					  @media (max-width: 767.98px) {
 | 
				
			||||||
 | 
					    width: 50px;
 | 
				
			||||||
 | 
					    height: 50px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.view-link {
 | 
				
			||||||
 | 
					  margin-top: 30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ticker {
 | 
				
			||||||
 | 
					  color: grey;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import { Component, OnInit } from '@angular/core';
 | 
				
			||||||
 | 
					import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
				
			||||||
 | 
					import { combineLatest, Observable } from 'rxjs';
 | 
				
			||||||
 | 
					import { map, switchMap } from 'rxjs/operators';
 | 
				
			||||||
 | 
					import { ApiService } from 'src/app/services/api.service';
 | 
				
			||||||
 | 
					import { AssetsService } from 'src/app/services/assets.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-asset-group',
 | 
				
			||||||
 | 
					  templateUrl: './asset-group.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./asset-group.component.scss']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AssetGroupComponent implements OnInit {
 | 
				
			||||||
 | 
					  group$: Observable<any>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private route: ActivatedRoute,
 | 
				
			||||||
 | 
					    private apiService: ApiService,
 | 
				
			||||||
 | 
					    private assetsService: AssetsService,
 | 
				
			||||||
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					    this.group$ = this.route.paramMap
 | 
				
			||||||
 | 
					      .pipe(
 | 
				
			||||||
 | 
					        switchMap((params: ParamMap) => {
 | 
				
			||||||
 | 
					          return combineLatest([
 | 
				
			||||||
 | 
					            this.assetsService.getAssetsJson$,
 | 
				
			||||||
 | 
					            this.apiService.getAssetGroup$(params.get('id')),
 | 
				
			||||||
 | 
					          ]);
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        map(([assets, group]) => {
 | 
				
			||||||
 | 
					          const items = [];
 | 
				
			||||||
 | 
					          // @ts-ignore
 | 
				
			||||||
 | 
					          for (const item of group.assets) {
 | 
				
			||||||
 | 
					            items.push(assets.objects[item]);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            group: group,
 | 
				
			||||||
 | 
					            assets: items
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					<div *ngIf="featuredAssets$ | async as featured; else loading" class="featuredBox">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="card" *ngFor="let group of featured">
 | 
				
			||||||
 | 
					    <ng-template [ngIf]="group.assets" [ngIfElse]="singleAsset">
 | 
				
			||||||
 | 
					      <a [routerLink]="['/assets/group' | relativeUrl, group.id]">
 | 
				
			||||||
 | 
					        <img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + group.assets[0] + '/icon'">
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					      <div class="title"><a [routerLink]="['/assets/group' | relativeUrl, group.id]">{{ group.name }}</a></div>
 | 
				
			||||||
 | 
					      <div class="sub-title" i18n>Group of {{ group.assets.length | number }} assets</div>
 | 
				
			||||||
 | 
					    </ng-template>
 | 
				
			||||||
 | 
					    <ng-template #singleAsset>
 | 
				
			||||||
 | 
					      <a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">
 | 
				
			||||||
 | 
					        <img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + group.asset + '/icon'">
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					      <div class="title">
 | 
				
			||||||
 | 
					        <a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">{{ group.name }}</a>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="ticker">{{ group.ticker }}</div>
 | 
				
			||||||
 | 
					    </ng-template>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ng-template #loading>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <div class="text-center loadingGraphs">
 | 
				
			||||||
 | 
					    <div class="spinner-border text-light"></div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					.featuredBox {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-flow: row wrap;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  gap: 27px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.card {
 | 
				
			||||||
 | 
					  background-color: #1d1f31;
 | 
				
			||||||
 | 
					  width: 200px;
 | 
				
			||||||
 | 
					  height: 200px;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  @media (max-width: 767.98px) {
 | 
				
			||||||
 | 
					    width: 150px;
 | 
				
			||||||
 | 
					    height: 150px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.title {
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					  font-weight: bold;
 | 
				
			||||||
 | 
					  margin-top: 10px;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sub-title {
 | 
				
			||||||
 | 
					  color: grey;
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.assetIcon {
 | 
				
			||||||
 | 
					  width: 100px;
 | 
				
			||||||
 | 
					  height: 100px;
 | 
				
			||||||
 | 
					  @media (max-width: 767.98px) {
 | 
				
			||||||
 | 
					    width: 50px;
 | 
				
			||||||
 | 
					    height: 50px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.view-link {
 | 
				
			||||||
 | 
					  margin-top: 30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ticker {
 | 
				
			||||||
 | 
					  color: grey;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import { Component, OnInit } from '@angular/core';
 | 
				
			||||||
 | 
					import { Observable } from 'rxjs';
 | 
				
			||||||
 | 
					import { ApiService } from 'src/app/services/api.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-assets-featured',
 | 
				
			||||||
 | 
					  templateUrl: './assets-featured.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./assets-featured.component.scss']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AssetsFeaturedComponent implements OnInit {
 | 
				
			||||||
 | 
					  featuredAssets$: Observable<any>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private apiService: ApiService,
 | 
				
			||||||
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					    this.featuredAssets$ = this.apiService.listFeaturedAssets$();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<div class="container-xl">
 | 
				
			||||||
 | 
					  <div class="title-asset">
 | 
				
			||||||
 | 
					    <h1 i18n="Assets page header">Assets</h1>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="nav-container">
 | 
				
			||||||
 | 
					    <ul class="nav nav-pills">
 | 
				
			||||||
 | 
					      <li class="nav-item">
 | 
				
			||||||
 | 
					        <a class="nav-link" [routerLink]="['/assets/featured' | relativeUrl]" routerLinkActive="active" i18n>Featured</a>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      </li>
 | 
				
			||||||
 | 
					      <li class="nav-item">
 | 
				
			||||||
 | 
					        <a class="nav-link" [routerLink]="['/assets/all' | relativeUrl]" routerLinkActive="active" i18n>All</a>
 | 
				
			||||||
 | 
					      </li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <form [formGroup]="searchForm">
 | 
				
			||||||
 | 
					      <div class="input-group mb-2">
 | 
				
			||||||
 | 
					        <input #instance="ngbTypeahead" [ngbTypeahead]="typeaheadSearchFn" [resultFormatter]="formatterFn" (selectItem)="itemSelected()" (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="Search Assets Placeholder Text" placeholder="Search asset">
 | 
				
			||||||
 | 
					        <div class="input-group-append">
 | 
				
			||||||
 | 
					          <button [disabled]="!searchForm.get('searchText')?.value.length" class="btn btn-secondary" type="button" (click)="searchForm.get('searchText')?.setValue('');" autocomplete="off" i18n="Search Clear Button">Clear</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="clearfix"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <router-outlet></router-outlet>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<br>
 | 
				
			||||||
@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					ul {
 | 
				
			||||||
 | 
					  margin-bottom: 20px;
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					form {
 | 
				
			||||||
 | 
					  float: right;
 | 
				
			||||||
 | 
					  width: 300px;
 | 
				
			||||||
 | 
					  @media (max-width: 767.98px) {
 | 
				
			||||||
 | 
					    width: 90%;
 | 
				
			||||||
 | 
					    margin-bottom: 15px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 767.98px) {
 | 
				
			||||||
 | 
					  .nav-container {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					import { Component, OnInit, ViewChild } from '@angular/core';
 | 
				
			||||||
 | 
					import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 | 
				
			||||||
 | 
					import { Router } from '@angular/router';
 | 
				
			||||||
 | 
					import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
 | 
				
			||||||
 | 
					import { merge, Observable, of, Subject } from 'rxjs';
 | 
				
			||||||
 | 
					import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
 | 
				
			||||||
 | 
					import { AssetExtended } from 'src/app/interfaces/electrs.interface';
 | 
				
			||||||
 | 
					import { AssetsService } from 'src/app/services/assets.service';
 | 
				
			||||||
 | 
					import { SeoService } from 'src/app/services/seo.service';
 | 
				
			||||||
 | 
					import { StateService } from 'src/app/services/state.service';
 | 
				
			||||||
 | 
					import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
				
			||||||
 | 
					import { environment } from 'src/environments/environment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-assets-nav',
 | 
				
			||||||
 | 
					  templateUrl: './assets-nav.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./assets-nav.component.scss']
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AssetsNavComponent implements OnInit {
 | 
				
			||||||
 | 
					  @ViewChild('instance', {static: true}) instance: NgbTypeahead;
 | 
				
			||||||
 | 
					  nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
 | 
				
			||||||
 | 
					  searchForm: FormGroup;
 | 
				
			||||||
 | 
					  assetsCache: AssetExtended[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  typeaheadSearchFn: ((text: Observable<string>) => Observable<readonly any[]>);
 | 
				
			||||||
 | 
					  formatterFn = (asset: AssetExtended) => asset.name + ' (' + asset.ticker  + ')';
 | 
				
			||||||
 | 
					  focus$ = new Subject<string>();
 | 
				
			||||||
 | 
					  click$ = new Subject<string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  itemsPerPage = 15;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private formBuilder: FormBuilder,
 | 
				
			||||||
 | 
					    private seoService: SeoService,
 | 
				
			||||||
 | 
					    private router: Router,
 | 
				
			||||||
 | 
					    private assetsService: AssetsService,
 | 
				
			||||||
 | 
					    private stateService: StateService,
 | 
				
			||||||
 | 
					    private relativeUrlPipe: RelativeUrlPipe,
 | 
				
			||||||
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnInit(): void {
 | 
				
			||||||
 | 
					    this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`);
 | 
				
			||||||
 | 
					    this.typeaheadSearchFn = this.typeaheadSearch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.searchForm = this.formBuilder.group({
 | 
				
			||||||
 | 
					      searchText: [{ value: '', disabled: false }, Validators.required]
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  typeaheadSearch = (text$: Observable<string>) => {
 | 
				
			||||||
 | 
					    const debouncedText$ = text$.pipe(
 | 
				
			||||||
 | 
					      distinctUntilChanged()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
 | 
				
			||||||
 | 
					    const inputFocus$ = this.focus$;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$)
 | 
				
			||||||
 | 
					      .pipe(
 | 
				
			||||||
 | 
					        switchMap((searchText) => {
 | 
				
			||||||
 | 
					          if (!searchText.length) {
 | 
				
			||||||
 | 
					            return of([]);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return this.assetsService.getAssetsJson$.pipe(
 | 
				
			||||||
 | 
					            map((assets) => {
 | 
				
			||||||
 | 
					              if (searchText.length ) {
 | 
				
			||||||
 | 
					                const filteredAssets = assets.array.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
 | 
				
			||||||
 | 
					                  || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1
 | 
				
			||||||
 | 
					                  || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1);
 | 
				
			||||||
 | 
					                return filteredAssets.slice(0, this.itemsPerPage);
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                return assets.array.slice(0, this.itemsPerPage);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  itemSelected() {
 | 
				
			||||||
 | 
					    setTimeout(() => this.search());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  search() {
 | 
				
			||||||
 | 
					    const searchText = this.searchForm.value.searchText;
 | 
				
			||||||
 | 
					    this.navigate('/assets/asset/', searchText.asset_id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  navigate(url: string, searchText: string, extras?: any) {
 | 
				
			||||||
 | 
					    this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras);
 | 
				
			||||||
 | 
					    this.searchForm.setValue({
 | 
				
			||||||
 | 
					      searchText: '',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										52
									
								
								frontend/src/app/components/assets/assets.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/src/app/components/assets/assets.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					<ng-container *ngIf="(assets$ | async) as filteredAssets; else isLoading">
 | 
				
			||||||
 | 
					  <table class="table table-borderless table-striped">
 | 
				
			||||||
 | 
					    <thead>
 | 
				
			||||||
 | 
					      <th class="td-name" i18n="Asset name header">Name</th>
 | 
				
			||||||
 | 
					      <th i18n="Asset ticker header">Ticker</th>
 | 
				
			||||||
 | 
					      <th class="d-none d-md-table-cell" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
				
			||||||
 | 
					      <th class="d-none d-lg-table-cell" i18n="Asset ID header">Asset ID</th>
 | 
				
			||||||
 | 
					    </thead>
 | 
				
			||||||
 | 
					    <tbody>
 | 
				
			||||||
 | 
					      <tr *ngFor="let asset of filteredAssets; trackBy: trackByAsset">
 | 
				
			||||||
 | 
					        <td class="td-name"><a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a></td>
 | 
				
			||||||
 | 
					        <td>{{ asset.ticker }}</td>
 | 
				
			||||||
 | 
					        <td class="d-none d-md-table-cell">{{ asset.entity && asset.entity.domain }}</td>
 | 
				
			||||||
 | 
					        <td class="d-none d-lg-table-cell"><a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
 | 
				
			||||||
 | 
					      </tr>
 | 
				
			||||||
 | 
					    </tbody>
 | 
				
			||||||
 | 
					  </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ngb-pagination [collectionSize]="assets.length" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="ellipses"></ngb-pagination>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ng-template #isLoading>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <table class="table table-borderless table-striped">
 | 
				
			||||||
 | 
					    <thead>
 | 
				
			||||||
 | 
					      <th i18n="Asset name header">Name</th>
 | 
				
			||||||
 | 
					      <th i18n="Asset ticker header">Ticker</th>
 | 
				
			||||||
 | 
					      <th class="d-none d-md-table-cell" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
				
			||||||
 | 
					      <th class="d-none d-lg-table-cell" i18n="Asset ID header">Asset ID</th>
 | 
				
			||||||
 | 
					    </thead>
 | 
				
			||||||
 | 
					    <tbody>
 | 
				
			||||||
 | 
					      <tr *ngFor="let dummy of [0,0,0,0,0,0,0,0,0,0]">
 | 
				
			||||||
 | 
					        <td><span class="skeleton-loader"></span></td>
 | 
				
			||||||
 | 
					        <td><span class="skeleton-loader"></span></td>
 | 
				
			||||||
 | 
					        <td class="d-none d-md-table-cell"><span class="skeleton-loader"></span></td>
 | 
				
			||||||
 | 
					        <td class="d-none d-lg-table-cell"><span class="skeleton-loader"></span></td>
 | 
				
			||||||
 | 
					      </tr>
 | 
				
			||||||
 | 
					    </tbody>
 | 
				
			||||||
 | 
					  </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ng-template [ngIf]="error">
 | 
				
			||||||
 | 
					  <div class="text-center">
 | 
				
			||||||
 | 
					    <ng-container i18n="Asset data load error">Error loading assets data.</ng-container>
 | 
				
			||||||
 | 
					    <br>
 | 
				
			||||||
 | 
					    <i>{{ error.error }}</i>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
							
								
								
									
										99
									
								
								frontend/src/app/components/assets/assets.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								frontend/src/app/components/assets/assets.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
 | 
				
			||||||
 | 
					import { AssetsService } from 'src/app/services/assets.service';
 | 
				
			||||||
 | 
					import { environment } from 'src/environments/environment';
 | 
				
			||||||
 | 
					import { FormGroup } from '@angular/forms';
 | 
				
			||||||
 | 
					import { filter, map, switchMap, take } from 'rxjs/operators';
 | 
				
			||||||
 | 
					import { ActivatedRoute, Router } from '@angular/router';
 | 
				
			||||||
 | 
					import { combineLatest, Observable } from 'rxjs';
 | 
				
			||||||
 | 
					import { AssetExtended } from 'src/app/interfaces/electrs.interface';
 | 
				
			||||||
 | 
					import { SeoService } from 'src/app/services/seo.service';
 | 
				
			||||||
 | 
					import { StateService } from 'src/app/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;
 | 
				
			||||||
 | 
					  paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 4 : 6;
 | 
				
			||||||
 | 
					  ellipses = window.matchMedia('(max-width: 670px)').matches ? false : true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  assets: AssetExtended[];
 | 
				
			||||||
 | 
					  assetsCache: AssetExtended[];
 | 
				
			||||||
 | 
					  searchForm: FormGroup;
 | 
				
			||||||
 | 
					  assets$: Observable<AssetExtended[]>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  page = 1;
 | 
				
			||||||
 | 
					  error: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  itemsPerPage: number;
 | 
				
			||||||
 | 
					  contentSpace = window.innerHeight - (250 + 200);
 | 
				
			||||||
 | 
					  fiveItemsPxSize = 250;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private assetsService: AssetsService,
 | 
				
			||||||
 | 
					    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.assets$ = combineLatest([
 | 
				
			||||||
 | 
					      this.assetsService.getAssetsJson$,
 | 
				
			||||||
 | 
					      this.route.queryParams,
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					      .pipe(
 | 
				
			||||||
 | 
					        take(1),
 | 
				
			||||||
 | 
					        switchMap(([assets, qp]) => {
 | 
				
			||||||
 | 
					          this.assets = assets.array;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return this.route.queryParams
 | 
				
			||||||
 | 
					            .pipe(
 | 
				
			||||||
 | 
					              filter((queryParams) => {
 | 
				
			||||||
 | 
					                const newPage = parseInt(queryParams.page, 10);
 | 
				
			||||||
 | 
					                if (newPage !== this.page) {
 | 
				
			||||||
 | 
					                  return true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					              }),
 | 
				
			||||||
 | 
					              map((queryParams) => {
 | 
				
			||||||
 | 
					                if (queryParams.page) {
 | 
				
			||||||
 | 
					                  const newPage = parseInt(queryParams.page, 10);
 | 
				
			||||||
 | 
					                  this.page = newPage;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                  this.page = 1;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return '';
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        map(() => {
 | 
				
			||||||
 | 
					          const start = (this.page - 1) * this.itemsPerPage;
 | 
				
			||||||
 | 
					          return this.assets.slice(start, this.itemsPerPage + start);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pageChange(page: number) {
 | 
				
			||||||
 | 
					    const queryParams = { page: page };
 | 
				
			||||||
 | 
					    if (queryParams.page === 1) {
 | 
				
			||||||
 | 
					      queryParams.page = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.page = -1;
 | 
				
			||||||
 | 
					    this.router.navigate([], {
 | 
				
			||||||
 | 
					      relativeTo: this.route,
 | 
				
			||||||
 | 
					      queryParams: queryParams,
 | 
				
			||||||
 | 
					      queryParamsHandling: 'merge',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  trackByAsset(index: number, asset: any) {
 | 
				
			||||||
 | 
					    return asset.asset_id;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -105,11 +105,11 @@ export class SearchFormComponent implements OnInit {
 | 
				
			|||||||
        const matches = this.regexTransaction.exec(searchText);
 | 
					        const matches = this.regexTransaction.exec(searchText);
 | 
				
			||||||
        if (this.network === 'liquid' || this.network === 'liquidtestnet') {
 | 
					        if (this.network === 'liquid' || this.network === 'liquidtestnet') {
 | 
				
			||||||
          if (this.assets[matches[1]]) {
 | 
					          if (this.assets[matches[1]]) {
 | 
				
			||||||
            this.navigate('/asset/', matches[1]);
 | 
					            this.navigate('/assets/asset/', matches[1]);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          this.electrsApiService.getAsset$(matches[1])
 | 
					          this.electrsApiService.getAsset$(matches[1])
 | 
				
			||||||
            .subscribe(
 | 
					            .subscribe(
 | 
				
			||||||
              () => { this.navigate('/asset/', matches[1]); },
 | 
					              () => { this.navigate('/assets/asset/', matches[1]); },
 | 
				
			||||||
              () => {
 | 
					              () => {
 | 
				
			||||||
                this.electrsApiService.getBlock$(matches[1])
 | 
					                this.electrsApiService.getBlock$(matches[1])
 | 
				
			||||||
                  .subscribe(
 | 
					                  .subscribe(
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@
 | 
				
			|||||||
        [height]="600"
 | 
					        [height]="600"
 | 
				
			||||||
        [left]="60"
 | 
					        [left]="60"
 | 
				
			||||||
        [right]="10"
 | 
					        [right]="10"
 | 
				
			||||||
        [data]="mempoolStats && mempoolStats.length ? mempoolStats : null"
 | 
					        [data]="statsSubscription$ | async"
 | 
				
			||||||
        [showZoom]="false"
 | 
					        [showZoom]="false"
 | 
				
			||||||
      ></app-mempool-graph>
 | 
					      ></app-mempool-graph>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,39 +4,70 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
 | 
				
			|||||||
import { StateService } from 'src/app/services/state.service';
 | 
					import { StateService } from 'src/app/services/state.service';
 | 
				
			||||||
import { ApiService } from 'src/app/services/api.service';
 | 
					import { ApiService } from 'src/app/services/api.service';
 | 
				
			||||||
import { SeoService } from 'src/app/services/seo.service';
 | 
					import { SeoService } from 'src/app/services/seo.service';
 | 
				
			||||||
import { Observable } from 'rxjs';
 | 
					import { ActivatedRoute } from '@angular/router';
 | 
				
			||||||
 | 
					import { map, scan, startWith, switchMap, tap } from 'rxjs/operators';
 | 
				
			||||||
 | 
					import { interval, merge, Observable } from 'rxjs';
 | 
				
			||||||
 | 
					import { ChangeDetectionStrategy } from '@angular/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-television',
 | 
					  selector: 'app-television',
 | 
				
			||||||
  templateUrl: './television.component.html',
 | 
					  templateUrl: './television.component.html',
 | 
				
			||||||
  styleUrls: ['./television.component.scss']
 | 
					  styleUrls: ['./television.component.scss'],
 | 
				
			||||||
 | 
					  changeDetection: ChangeDetectionStrategy.OnPush
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class TelevisionComponent implements OnInit {
 | 
					export class TelevisionComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mempoolStats: OptimizedMempoolStats[] = [];
 | 
					  mempoolStats: OptimizedMempoolStats[] = [];
 | 
				
			||||||
  mempoolVsizeFeesData: any;
 | 
					  statsSubscription$: Observable<OptimizedMempoolStats[]>;
 | 
				
			||||||
 | 
					  fragment: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private websocketService: WebsocketService,
 | 
					    private websocketService: WebsocketService,
 | 
				
			||||||
    private apiService: ApiService,
 | 
					    private apiService: ApiService,
 | 
				
			||||||
    private stateService: StateService,
 | 
					    private stateService: StateService,
 | 
				
			||||||
    private seoService: SeoService,
 | 
					    private seoService: SeoService,
 | 
				
			||||||
 | 
					    private route: ActivatedRoute
 | 
				
			||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  refreshStats(time: number, fn: Observable<OptimizedMempoolStats[]>) {
 | 
				
			||||||
 | 
					    return interval(time).pipe(startWith(0), switchMap(() => fn));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit() {
 | 
					  ngOnInit() {
 | 
				
			||||||
    this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`);
 | 
					    this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`);
 | 
				
			||||||
    this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']);
 | 
					    this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.apiService.list2HStatistics$()
 | 
					    this.statsSubscription$ = merge(
 | 
				
			||||||
      .subscribe((mempoolStats) => {
 | 
					      this.stateService.live2Chart$.pipe(map(stats => [stats])),
 | 
				
			||||||
        this.mempoolStats = mempoolStats;
 | 
					      this.route.fragment
 | 
				
			||||||
      });
 | 
					        .pipe(
 | 
				
			||||||
 | 
					          tap(fragment => { this.fragment = fragment ?? '2h'; }),
 | 
				
			||||||
    this.stateService.live2Chart$
 | 
					          switchMap((fragment) => {
 | 
				
			||||||
      .subscribe((mempoolStats) => {
 | 
					            const minute = 60000; const hour = 3600000;
 | 
				
			||||||
        this.mempoolStats.unshift(mempoolStats);
 | 
					            switch (fragment) {
 | 
				
			||||||
        this.mempoolStats = this.mempoolStats.slice(0, this.mempoolStats.length - 1);
 | 
					              case '24h': return this.apiService.list24HStatistics$();
 | 
				
			||||||
      });
 | 
					              case '1w': return this.refreshStats(5 * minute, this.apiService.list1WStatistics$());
 | 
				
			||||||
 | 
					              case '1m': return this.refreshStats(30 * minute, this.apiService.list1MStatistics$());
 | 
				
			||||||
 | 
					              case '3m': return this.refreshStats(2 * hour, this.apiService.list3MStatistics$());
 | 
				
			||||||
 | 
					              case '6m': return this.refreshStats(3 * hour, this.apiService.list6MStatistics$());
 | 
				
			||||||
 | 
					              case '1y': return this.refreshStats(8 * hour, this.apiService.list1YStatistics$());
 | 
				
			||||||
 | 
					              case '2y': return this.refreshStats(8 * hour, this.apiService.list2YStatistics$());
 | 
				
			||||||
 | 
					              case '3y': return this.refreshStats(12 * hour, this.apiService.list3YStatistics$());
 | 
				
			||||||
 | 
					              default /* 2h */: return this.apiService.list2HStatistics$();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .pipe(
 | 
				
			||||||
 | 
					      scan((mempoolStats, newStats) => {
 | 
				
			||||||
 | 
					        if (newStats.length > 1) {
 | 
				
			||||||
 | 
					          mempoolStats = newStats;
 | 
				
			||||||
 | 
					        } else if (['2h', '24h'].includes(this.fragment)) {
 | 
				
			||||||
 | 
					          mempoolStats.unshift(newStats[0]);
 | 
				
			||||||
 | 
					          mempoolStats = mempoolStats.slice(0, mempoolStats.length - 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return mempoolStats;
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -274,5 +274,5 @@
 | 
				
			|||||||
  <br />
 | 
					  <br />
 | 
				
			||||||
  {{ assetsMinimal[item.asset][0] }}
 | 
					  {{ assetsMinimal[item.asset][0] }}
 | 
				
			||||||
  <br />
 | 
					  <br />
 | 
				
			||||||
  <a [routerLink]="['/asset/' | relativeUrl, item.asset]">{{ item.asset | shortenString : 13 }}</a>
 | 
					  <a [routerLink]="['/assets/asset/' | relativeUrl, item.asset]">{{ item.asset | shortenString : 13 }}</a>
 | 
				
			||||||
</ng-template>
 | 
					</ng-template>
 | 
				
			||||||
 | 
				
			|||||||
@ -117,6 +117,14 @@ export class ApiService {
 | 
				
			|||||||
    return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
 | 
					    return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  listFeaturedAssets$(): Observable<any[]> {
 | 
				
			||||||
 | 
					    return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/featured');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getAssetGroup$(id: string): Observable<any> {
 | 
				
			||||||
 | 
					    return this.httpClient.get<any[]>(this.apiBaseUrl + '/api/v1/assets/group/' + id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  postTransaction$(hexPayload: string): Observable<any> {
 | 
					  postTransaction$(hexPayload: string): Observable<any> {
 | 
				
			||||||
    return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
 | 
					    return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -3,12 +3,16 @@ import { HttpClient } from '@angular/common/http';
 | 
				
			|||||||
import { Observable } from 'rxjs';
 | 
					import { Observable } from 'rxjs';
 | 
				
			||||||
import { map, shareReplay, switchMap } from 'rxjs/operators';
 | 
					import { map, shareReplay, switchMap } from 'rxjs/operators';
 | 
				
			||||||
import { StateService } from './state.service';
 | 
					import { StateService } from './state.service';
 | 
				
			||||||
 | 
					import { environment } from 'src/environments/environment';
 | 
				
			||||||
 | 
					import { AssetExtended } from '../interfaces/electrs.interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable({
 | 
					@Injectable({
 | 
				
			||||||
  providedIn: 'root'
 | 
					  providedIn: 'root'
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AssetsService {
 | 
					export class AssetsService {
 | 
				
			||||||
  getAssetsJson$: Observable<any>;
 | 
					  nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getAssetsJson$: Observable<{ array: AssetExtended[]; objects: any}>;
 | 
				
			||||||
  getAssetsMinimalJson$: Observable<any>;
 | 
					  getAssetsMinimalJson$: Observable<any>;
 | 
				
			||||||
  getMiningPools$: Observable<any>;
 | 
					  getMiningPools$: Observable<any>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -24,6 +28,30 @@ export class AssetsService {
 | 
				
			|||||||
    this.getAssetsJson$ = this.stateService.networkChanged$
 | 
					    this.getAssetsJson$ = this.stateService.networkChanged$
 | 
				
			||||||
      .pipe(
 | 
					      .pipe(
 | 
				
			||||||
        switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)),
 | 
					        switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)),
 | 
				
			||||||
 | 
					        map((rawAssets) => {
 | 
				
			||||||
 | 
					          const assets: AssetExtended[] = Object.values(rawAssets);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					          if (this.stateService.network === 'liquid') {
 | 
				
			||||||
 | 
					            // @ts-ignore
 | 
				
			||||||
 | 
					            assets.push({
 | 
				
			||||||
 | 
					              name: 'Liquid Bitcoin',
 | 
				
			||||||
 | 
					              ticker: 'L-BTC',
 | 
				
			||||||
 | 
					              asset_id: this.nativeAssetId,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          } else if (this.stateService.network === 'liquidtestnet') {
 | 
				
			||||||
 | 
					            // @ts-ignore
 | 
				
			||||||
 | 
					            assets.push({
 | 
				
			||||||
 | 
					              name: 'Test Liquid Bitcoin',
 | 
				
			||||||
 | 
					              ticker: 'tL-BTC',
 | 
				
			||||||
 | 
					              asset_id: this.nativeAssetId,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            objects: rawAssets,
 | 
				
			||||||
 | 
					            array: assets.sort((a: any, b: any) => a.name.localeCompare(b.name)),
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
        shareReplay(1),
 | 
					        shareReplay(1),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    this.getAssetsMinimalJson$ = this.stateService.networkChanged$
 | 
					    this.getAssetsMinimalJson$ = this.stateService.networkChanged$
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,35 @@ function download(filename, url) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function downloadMiningPoolLogos() {
 | 
				
			||||||
 | 
					  const options = {
 | 
				
			||||||
 | 
					    host: 'api.github.com',
 | 
				
			||||||
 | 
					    path: '/repos/mempool/mining-pools/contents/',
 | 
				
			||||||
 | 
					    method: 'GET',
 | 
				
			||||||
 | 
					    headers: {'user-agent': 'node.js'}
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  https.get(options, (response) => {
 | 
				
			||||||
 | 
					    let chunks_of_data = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response.on('data', (fragments) => {
 | 
				
			||||||
 | 
					      chunks_of_data.push(fragments);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    response.on('end', () => {
 | 
				
			||||||
 | 
					      let response_body = Buffer.concat(chunks_of_data);
 | 
				
			||||||
 | 
					      const poolLogos = JSON.parse(response_body.toString());
 | 
				
			||||||
 | 
					      for (const poolLogo of poolLogos) {
 | 
				
			||||||
 | 
					          download(`${PATH}/mining-pools/${poolLogo.name}`, poolLogo.download_url);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    response.on('error', (error) => {
 | 
				
			||||||
 | 
					      throw new Error(error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const poolsJsonUrl = 'https://raw.githubusercontent.com/btccom/Blockchain-Known-Pools/master/pools.json';
 | 
					const poolsJsonUrl = 'https://raw.githubusercontent.com/btccom/Blockchain-Known-Pools/master/pools.json';
 | 
				
			||||||
let assetsJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.json';
 | 
					let assetsJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.json';
 | 
				
			||||||
let assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.minimal.json';
 | 
					let assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.minimal.json';
 | 
				
			||||||
@ -55,4 +84,5 @@ console.log('Downloading testnet assets');
 | 
				
			|||||||
download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl);
 | 
					download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl);
 | 
				
			||||||
console.log('Downloading testnet assets minimal');
 | 
					console.log('Downloading testnet assets minimal');
 | 
				
			||||||
download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl);
 | 
					download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl);
 | 
				
			||||||
 | 
					console.log('Downloading mining pool logos');
 | 
				
			||||||
 | 
					downloadMiningPoolLogos();
 | 
				
			||||||
 | 
				
			|||||||
@ -70,3 +70,15 @@ location /api/v1/translators {
 | 
				
			|||||||
	proxy_hide_header content-security-policy;
 | 
						proxy_hide_header content-security-policy;
 | 
				
			||||||
	proxy_hide_header x-frame-options;
 | 
						proxy_hide_header x-frame-options;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					location /api/v1/assets {
 | 
				
			||||||
 | 
						proxy_pass $mempoolSpaceServices;
 | 
				
			||||||
 | 
						proxy_cache services;
 | 
				
			||||||
 | 
						proxy_cache_background_update on;
 | 
				
			||||||
 | 
						proxy_cache_use_stale updating;
 | 
				
			||||||
 | 
						proxy_cache_valid 200 10m;
 | 
				
			||||||
 | 
						expires 10m;
 | 
				
			||||||
 | 
						proxy_hide_header onion-location;
 | 
				
			||||||
 | 
						proxy_hide_header strict-transport-security;
 | 
				
			||||||
 | 
						proxy_hide_header content-security-policy;
 | 
				
			||||||
 | 
						proxy_hide_header x-frame-options;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user