mirror of
https://github.com/mempool/mempool.git
synced 2024-12-29 09:44:26 +01:00
Merge branch 'master' into fee-visibility
This commit is contained in:
commit
89d811096c
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™ [![mempool](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/ry4br7/master&style=flat-square)](https://dashboard.cypress.io/projects/ry4br7/runs)
|
||||||
|
|
||||||
Mempool is the fully featured visualizer, explorer, and API service running on [mempool.space](https://mempool.space/), an open source project developed and operated for the benefit of the Bitcoin community, with a focus on the emerging transaction fee market to help our transition into a multi-layer ecosystem.
|
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"
|
||||||
},
|
},
|
||||||
|
@ -96,14 +96,20 @@ class Blocks {
|
|||||||
*/
|
*/
|
||||||
private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended {
|
private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended {
|
||||||
const blockExtended: BlockExtended = Object.assign({}, block);
|
const blockExtended: BlockExtended = Object.assign({}, block);
|
||||||
blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
|
||||||
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
blockExtended.extras = {
|
||||||
|
reward: transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0),
|
||||||
|
coinbaseTx: transactionUtils.stripCoinbaseTransaction(transactions[0]),
|
||||||
|
};
|
||||||
|
|
||||||
const transactionsTmp = [...transactions];
|
const transactionsTmp = [...transactions];
|
||||||
transactionsTmp.shift();
|
transactionsTmp.shift();
|
||||||
transactionsTmp.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
|
transactionsTmp.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
|
||||||
blockExtended.medianFee = transactionsTmp.length > 0 ? Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0;
|
|
||||||
blockExtended.feeRange = transactionsTmp.length > 0 ? Common.getFeesInRange(transactionsTmp, 8) : [0, 0];
|
blockExtended.extras.medianFee = transactionsTmp.length > 0 ?
|
||||||
|
Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0;
|
||||||
|
blockExtended.extras.feeRange = transactionsTmp.length > 0 ?
|
||||||
|
Common.getFeesInRange(transactionsTmp, 8) : [0, 0];
|
||||||
|
|
||||||
return blockExtended;
|
return blockExtended;
|
||||||
}
|
}
|
||||||
@ -197,7 +203,14 @@ class Blocks {
|
|||||||
const block = await bitcoinApi.$getBlock(blockHash);
|
const block = await bitcoinApi.$getBlock(blockHash);
|
||||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
||||||
const blockExtended = this.getBlockExtended(block, transactions);
|
const blockExtended = this.getBlockExtended(block, transactions);
|
||||||
const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
|
|
||||||
|
let miner: PoolTag;
|
||||||
|
if (blockExtended?.extras?.coinbaseTx) {
|
||||||
|
miner = await this.$findBlockMiner(blockExtended.extras.coinbaseTx);
|
||||||
|
} else {
|
||||||
|
miner = await poolsRepository.$getUnknownPool();
|
||||||
|
}
|
||||||
|
|
||||||
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
||||||
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
|
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -262,7 +275,12 @@ class Blocks {
|
|||||||
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
||||||
|
|
||||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === true) {
|
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === true) {
|
||||||
const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
|
let miner: PoolTag;
|
||||||
|
if (blockExtended?.extras?.coinbaseTx) {
|
||||||
|
miner = await this.$findBlockMiner(blockExtended.extras.coinbaseTx);
|
||||||
|
} else {
|
||||||
|
miner = await poolsRepository.$getUnknownPool();
|
||||||
|
}
|
||||||
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
|
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -380,7 +380,9 @@ class WebsocketHandler {
|
|||||||
mBlocks = mempoolBlocks.getMempoolBlocks();
|
mBlocks = mempoolBlocks.getMempoolBlocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
block.matchRate = matchRate;
|
if (block.extras) {
|
||||||
|
block.extras.matchRate = matchRate;
|
||||||
|
}
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
|
@ -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';
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,8 @@ export interface TransactionStripped {
|
|||||||
vsize: number;
|
vsize: number;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
export interface BlockExtended extends IEsploraApi.Block {
|
|
||||||
|
export interface BlockExtension {
|
||||||
medianFee?: number;
|
medianFee?: number;
|
||||||
feeRange?: number[];
|
feeRange?: number[];
|
||||||
reward?: number;
|
reward?: number;
|
||||||
@ -84,6 +85,10 @@ export interface BlockExtended extends IEsploraApi.Block {
|
|||||||
matchRate?: number;
|
matchRate?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlockExtended extends IEsploraApi.Block {
|
||||||
|
extras?: BlockExtension;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TransactionMinerInfo {
|
export interface TransactionMinerInfo {
|
||||||
vin: VinStrippedToScriptsig[];
|
vin: VinStrippedToScriptsig[];
|
||||||
vout: VoutStrippedToScriptPubkey[];
|
vout: VoutStrippedToScriptPubkey[];
|
||||||
|
@ -31,9 +31,18 @@ class BlocksRepository {
|
|||||||
)`;
|
)`;
|
||||||
|
|
||||||
const params: any[] = [
|
const params: any[] = [
|
||||||
block.height, blockHash, block.timestamp, block.size,
|
block.height,
|
||||||
block.weight, block.tx_count, coinbaseHex ? coinbaseHex : '', block.difficulty,
|
blockHash,
|
||||||
poolTag.id, 0, '[]', block.medianFee,
|
block.timestamp,
|
||||||
|
block.size,
|
||||||
|
block.weight,
|
||||||
|
block.tx_count,
|
||||||
|
coinbaseHex ? coinbaseHex : '',
|
||||||
|
block.difficulty,
|
||||||
|
poolTag.id,
|
||||||
|
0,
|
||||||
|
'[]',
|
||||||
|
block.extras ? block.extras.medianFee : 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
await connection.query(query, params);
|
await connection.query(query, params);
|
||||||
|
@ -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
|
||||||
|
@ -218,6 +218,10 @@
|
|||||||
"proxyConfig": "proxy.conf.local.js",
|
"proxyConfig": "proxy.conf.local.js",
|
||||||
"verbose": true
|
"verbose": true
|
||||||
},
|
},
|
||||||
|
"mixed": {
|
||||||
|
"proxyConfig": "proxy.conf.mixed.js",
|
||||||
|
"verbose": true
|
||||||
|
},
|
||||||
"staging": {
|
"staging": {
|
||||||
"proxyConfig": "proxy.conf.js",
|
"proxyConfig": "proxy.conf.js",
|
||||||
"disableHostCheck": true,
|
"disableHostCheck": true,
|
||||||
@ -229,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,12 @@
|
|||||||
"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",
|
||||||
"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",
|
||||||
"sync-assets-dev": "node sync-assets.js dev",
|
"sync-assets-dev": "node sync-assets.js dev",
|
||||||
@ -53,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",
|
||||||
|
@ -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") {
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
let PROXY_CONFIG = require('./proxy.conf.js');
|
|
||||||
const BACKEND_CONFIG_FILE_NAME = '../backend/mempool-config.json';
|
|
||||||
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||||
|
|
||||||
let backendConfigContent;
|
let configContent;
|
||||||
let frontendConfigContent;
|
|
||||||
|
|
||||||
// Read frontend config
|
// Read frontend config
|
||||||
try {
|
try {
|
||||||
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
|
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
|
||||||
frontendConfigContent = JSON.parse(rawConfig);
|
configContent = JSON.parse(rawConfig);
|
||||||
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
|
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
@ -22,51 +18,88 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read backend config
|
let PROXY_CONFIG = [];
|
||||||
try {
|
|
||||||
const rawConfig = fs.readFileSync(BACKEND_CONFIG_FILE_NAME);
|
if (configContent && configContent.BASE_MODULE === 'liquid') {
|
||||||
backendConfigContent = JSON.parse(rawConfig);
|
PROXY_CONFIG.push(...[
|
||||||
console.log(`${BACKEND_CONFIG_FILE_NAME} file found, using provided config`);
|
{
|
||||||
} catch (e) {
|
context: ['/liquid/api/v1/**'],
|
||||||
console.log(e);
|
target: `http://localhost:8999`,
|
||||||
if (e.code !== 'ENOENT') {
|
secure: false,
|
||||||
throw new Error(e);
|
ws: true,
|
||||||
} else {
|
changeOrigin: true,
|
||||||
console.log(`${BACKEND_CONFIG_FILE_NAME} file not found, using default config`);
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/liquid": ""
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/liquid/api/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/liquid/api/": "/api/v1/"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (configContent && configContent.BASE_MODULE === 'bisq') {
|
||||||
|
PROXY_CONFIG.push(...[
|
||||||
|
{
|
||||||
|
context: ['/bisq/api/v1/ws'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/bisq": ""
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/bisq/api/v1/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/bisq/api/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/bisq/api/": "/api/v1/bisq/"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
PROXY_CONFIG.push(...[
|
||||||
|
{
|
||||||
|
context: ['/api/v1/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/api/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/api/": "/api/v1/"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
]);
|
||||||
|
|
||||||
// Remove the "/api/**" entry from the default proxy config
|
|
||||||
let localDevContext = PROXY_CONFIG[0].context
|
|
||||||
|
|
||||||
localDevContext.splice(PROXY_CONFIG[0].context.indexOf('/api/**'), 1);
|
|
||||||
|
|
||||||
PROXY_CONFIG[0].context = localDevContext;
|
|
||||||
|
|
||||||
// Change all targets to localhost
|
|
||||||
PROXY_CONFIG.map(conf => conf.target = "http://localhost:8999");
|
|
||||||
|
|
||||||
// Add rules for local backend
|
|
||||||
if (backendConfigContent) {
|
|
||||||
PROXY_CONFIG.push({
|
|
||||||
context: ['/api/address/**', '/api/tx/**', '/api/block/**', '/api/blocks/**'],
|
|
||||||
target: `http://localhost:8999`,
|
|
||||||
secure: false,
|
|
||||||
changeOrigin: true,
|
|
||||||
proxyTimeout: 30000,
|
|
||||||
pathRewrite: {
|
|
||||||
"^/api/": "/api/v1/"
|
|
||||||
},
|
|
||||||
});
|
|
||||||
PROXY_CONFIG.push({
|
|
||||||
context: ['/api/v1/**'],
|
|
||||||
target: `http://localhost:8999`,
|
|
||||||
secure: false,
|
|
||||||
changeOrigin: true,
|
|
||||||
proxyTimeout: 30000
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(PROXY_CONFIG);
|
console.log(PROXY_CONFIG);
|
||||||
|
|
||||||
|
99
frontend/proxy.conf.mixed.js
Normal file
99
frontend/proxy.conf.mixed.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||||
|
|
||||||
|
let configContent;
|
||||||
|
|
||||||
|
// Read frontend config
|
||||||
|
try {
|
||||||
|
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
|
||||||
|
configContent = JSON.parse(rawConfig);
|
||||||
|
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
if (e.code !== 'ENOENT') {
|
||||||
|
throw new Error(e);
|
||||||
|
} else {
|
||||||
|
console.log(`${FRONTEND_CONFIG_FILE_NAME} file not found, using default config`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let PROXY_CONFIG = [];
|
||||||
|
|
||||||
|
if (configContent && configContent.BASE_MODULE === 'liquid') {
|
||||||
|
PROXY_CONFIG.push(...[
|
||||||
|
{
|
||||||
|
context: ['/liquid/api/v1/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/liquid": ""
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/liquid/api/**'],
|
||||||
|
target: `https://liquid.network`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configContent && configContent.BASE_MODULE === 'bisq') {
|
||||||
|
PROXY_CONFIG.push(...[
|
||||||
|
{
|
||||||
|
context: ['/bisq/api/v1/ws'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/bisq": ""
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/bisq/api/v1/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/bisq/api/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
pathRewrite: {
|
||||||
|
"^/bisq/api/": "/api/v1/bisq/"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
PROXY_CONFIG.push(...[
|
||||||
|
{
|
||||||
|
context: ['/api/v1/**'],
|
||||||
|
target: `http://localhost:8999`,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: ['/api/**'],
|
||||||
|
target: `https://mempool.space`,
|
||||||
|
secure: false,
|
||||||
|
changeOrigin: true,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log(PROXY_CONFIG);
|
||||||
|
|
||||||
|
module.exports = PROXY_CONFIG;
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -74,9 +74,9 @@
|
|||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngIf="block.medianFee !== undefined">
|
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||||
<td>~{{ block.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -3,12 +3,13 @@ import { Location } from '@angular/common';
|
|||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { switchMap, tap, debounceTime, catchError, map } from 'rxjs/operators';
|
import { switchMap, tap, debounceTime, catchError, map } from 'rxjs/operators';
|
||||||
import { Block, Transaction, Vout } from '../../interfaces/electrs.interface';
|
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||||
import { Observable, of, Subscription } from 'rxjs';
|
import { Observable, of, Subscription } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block',
|
selector: 'app-block',
|
||||||
@ -17,13 +18,13 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
|
|||||||
})
|
})
|
||||||
export class BlockComponent implements OnInit, OnDestroy {
|
export class BlockComponent implements OnInit, OnDestroy {
|
||||||
network = '';
|
network = '';
|
||||||
block: Block;
|
block: BlockExtended;
|
||||||
blockHeight: number;
|
blockHeight: number;
|
||||||
nextBlockHeight: number;
|
nextBlockHeight: number;
|
||||||
blockHash: string;
|
blockHash: string;
|
||||||
isLoadingBlock = true;
|
isLoadingBlock = true;
|
||||||
latestBlock: Block;
|
latestBlock: BlockExtended;
|
||||||
latestBlocks: Block[] = [];
|
latestBlocks: BlockExtended[] = [];
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
isLoadingTransactions = true;
|
isLoadingTransactions = true;
|
||||||
error: any;
|
error: any;
|
||||||
@ -76,7 +77,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
if (block.id === this.blockHash) {
|
if (block.id === this.blockHash) {
|
||||||
this.block = block;
|
this.block = block;
|
||||||
this.fees = block.reward / 100000000 - this.blockSubsidy;
|
if (block?.extras?.reward != undefined) {
|
||||||
|
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -108,7 +111,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.isLoadingBlock = true;
|
this.isLoadingBlock = true;
|
||||||
|
|
||||||
let blockInCache: Block;
|
let blockInCache: BlockExtended;
|
||||||
if (isBlockHeight) {
|
if (isBlockHeight) {
|
||||||
blockInCache = this.latestBlocks.find((block) => block.height === parseInt(blockHash, 10));
|
blockInCache = this.latestBlocks.find((block) => block.height === parseInt(blockHash, 10));
|
||||||
if (blockInCache) {
|
if (blockInCache) {
|
||||||
@ -134,7 +137,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
return this.electrsApiService.getBlock$(blockHash);
|
return this.electrsApiService.getBlock$(blockHash);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
tap((block: Block) => {
|
tap((block: BlockExtended) => {
|
||||||
this.block = block;
|
this.block = block;
|
||||||
this.blockHeight = block.height;
|
this.blockHeight = block.height;
|
||||||
this.nextBlockHeight = block.height + 1;
|
this.nextBlockHeight = block.height + 1;
|
||||||
@ -142,12 +145,10 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
||||||
this.isLoadingBlock = false;
|
this.isLoadingBlock = false;
|
||||||
if (block.coinbaseTx) {
|
this.coinbaseTx = block?.extras?.coinbaseTx;
|
||||||
this.coinbaseTx = block.coinbaseTx;
|
|
||||||
}
|
|
||||||
this.setBlockSubsidy();
|
this.setBlockSubsidy();
|
||||||
if (block.reward !== undefined) {
|
if (block?.extras?.reward !== undefined) {
|
||||||
this.fees = block.reward / 100000000 - this.blockSubsidy;
|
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
|
||||||
}
|
}
|
||||||
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
|
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
|
||||||
this.isLoadingTransactions = true;
|
this.isLoadingTransactions = true;
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="block-body">
|
<div class="block-body">
|
||||||
<div class="fees">
|
<div class="fees">
|
||||||
~{{ block.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="fee-span">
|
<div class="fee-span">
|
||||||
{{ block.feeRange[1] | number:feeRounding }} - {{ block.feeRange[block.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
{{ block?.extras?.feeRange[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
|
<div class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
|
||||||
<div class="transaction-count">
|
<div class="transaction-count">
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { Block } from 'src/app/interfaces/electrs.interface';
|
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { specialBlocks } from 'src/app/app.constants';
|
import { specialBlocks } from 'src/app/app.constants';
|
||||||
|
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-blockchain-blocks',
|
selector: 'app-blockchain-blocks',
|
||||||
@ -14,8 +14,8 @@ import { specialBlocks } from 'src/app/app.constants';
|
|||||||
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||||
specialBlocks = specialBlocks;
|
specialBlocks = specialBlocks;
|
||||||
network = '';
|
network = '';
|
||||||
blocks: Block[] = [];
|
blocks: BlockExtended[] = [];
|
||||||
emptyBlocks: Block[] = this.mountEmptyBlocks();
|
emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
|
||||||
markHeight: number;
|
markHeight: number;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
networkSubscription: Subscription;
|
networkSubscription: Subscription;
|
||||||
@ -69,8 +69,8 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
this.blocks.unshift(block);
|
this.blocks.unshift(block);
|
||||||
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
||||||
|
|
||||||
if (this.blocksFilled && !this.tabHidden) {
|
if (this.blocksFilled && !this.tabHidden && block.extras) {
|
||||||
block.stage = block.matchRate >= 66 ? 1 : 2;
|
block.extras.stage = block.extras.matchRate >= 66 ? 1 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txConfirmed) {
|
if (txConfirmed) {
|
||||||
@ -143,16 +143,16 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByBlocksFn(index: number, item: Block) {
|
trackByBlocksFn(index: number, item: BlockExtended) {
|
||||||
return item.height;
|
return item.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleForBlock(block: Block) {
|
getStyleForBlock(block: BlockExtended) {
|
||||||
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
|
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
|
||||||
let addLeft = 0;
|
let addLeft = 0;
|
||||||
|
|
||||||
if (block.stage === 1) {
|
if (block?.extras?.stage === 1) {
|
||||||
block.stage = 2;
|
block.extras.stage = 2;
|
||||||
addLeft = -205;
|
addLeft = -205;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,11 +167,11 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleForEmptyBlock(block: Block) {
|
getStyleForEmptyBlock(block: BlockExtended) {
|
||||||
let addLeft = 0;
|
let addLeft = 0;
|
||||||
|
|
||||||
if (block.stage === 1) {
|
if (block?.extras?.stage === 1) {
|
||||||
block.stage = 2;
|
block.extras.stage = 2;
|
||||||
addLeft = -205;
|
addLeft = -205;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.blockSubscription = this.stateService.blocks$
|
this.blockSubscription = this.stateService.blocks$
|
||||||
.subscribe(([block]) => {
|
.subscribe(([block]) => {
|
||||||
if (block.matchRate >= 66 && !this.tabHidden) {
|
if (block?.extras?.matchRate >= 66 && !this.tabHidden) {
|
||||||
this.blockIndex++;
|
this.blockIndex++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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(
|
||||||
|
@ -9,14 +9,14 @@ import {
|
|||||||
delay,
|
delay,
|
||||||
map
|
map
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { Transaction, Block } from '../../interfaces/electrs.interface';
|
import { Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
|
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { AudioService } from 'src/app/services/audio.service';
|
import { AudioService } from 'src/app/services/audio.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 { CpfpInfo } from 'src/app/interfaces/node-api.interface';
|
import { BlockExtended, CpfpInfo } from 'src/app/interfaces/node-api.interface';
|
||||||
import { LiquidUnblinding } from './liquid-ublinding';
|
import { LiquidUnblinding } from './liquid-ublinding';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -33,7 +33,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
error: any = undefined;
|
error: any = undefined;
|
||||||
errorUnblinded: any = undefined;
|
errorUnblinded: any = undefined;
|
||||||
waitingForTransaction = false;
|
waitingForTransaction = false;
|
||||||
latestBlock: Block;
|
latestBlock: BlockExtended;
|
||||||
transactionTime = -1;
|
transactionTime = -1;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
fetchCpfpSubscription: Subscription;
|
fetchCpfpSubscription: Subscription;
|
||||||
|
@ -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>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, Output, EventEmitter } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Observable, forkJoin } from 'rxjs';
|
import { Observable, forkJoin } from 'rxjs';
|
||||||
import { Block, Outspend, Transaction } from '../../interfaces/electrs.interface';
|
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { AssetsService } from 'src/app/services/assets.service';
|
import { AssetsService } from 'src/app/services/assets.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transactions-list',
|
selector: 'app-transactions-list',
|
||||||
@ -26,7 +27,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
@Output() loadMore = new EventEmitter();
|
@Output() loadMore = new EventEmitter();
|
||||||
|
|
||||||
latestBlock$: Observable<Block>;
|
latestBlock$: Observable<BlockExtended>;
|
||||||
outspends: Outspend[] = [];
|
outspends: Outspend[] = [];
|
||||||
assetsMinimal: any;
|
assetsMinimal: any;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Component, ChangeDetectionStrategy, OnChanges, Input, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, OnChanges, Input, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||||
import { Transaction, Block } from 'src/app/interfaces/electrs.interface';
|
import { Transaction } from 'src/app/interfaces/electrs.interface';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tx-fee-rating',
|
selector: 'app-tx-fee-rating',
|
||||||
@ -18,7 +19,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
overpaidTimes: number;
|
overpaidTimes: number;
|
||||||
feeRating: number;
|
feeRating: number;
|
||||||
|
|
||||||
blocks: Block[] = [];
|
blocks: BlockExtended[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
@ -28,7 +29,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.blocksSubscription = this.stateService.blocks$.subscribe(([block]) => {
|
this.blocksSubscription = this.stateService.blocks$.subscribe(([block]) => {
|
||||||
this.blocks.push(block);
|
this.blocks.push(block);
|
||||||
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block.medianFee > 0) {
|
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block?.extras?.medianFee > 0) {
|
||||||
this.calculateRatings(block);
|
this.calculateRatings(block);
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
@ -42,7 +43,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const foundBlock = this.blocks.find((b) => b.height === this.tx.status.block_height);
|
const foundBlock = this.blocks.find((b) => b.height === this.tx.status.block_height);
|
||||||
if (foundBlock && foundBlock.medianFee > 0) {
|
if (foundBlock && foundBlock?.extras?.medianFee > 0) {
|
||||||
this.calculateRatings(foundBlock);
|
this.calculateRatings(foundBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,9 +52,9 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateRatings(block: Block) {
|
calculateRatings(block: BlockExtended) {
|
||||||
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
|
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
|
||||||
this.medianFeeNeeded = block.medianFee;
|
this.medianFeeNeeded = block?.extras?.medianFee;
|
||||||
|
|
||||||
// Block not filled
|
// Block not filled
|
||||||
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {
|
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
||||||
import { combineLatest, merge, Observable, of, timer } from 'rxjs';
|
import { combineLatest, merge, Observable, of, timer } from 'rxjs';
|
||||||
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
|
||||||
import { Block } from '../interfaces/electrs.interface';
|
import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
|
||||||
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
import { ApiService } from '../services/api.service';
|
import { ApiService } from '../services/api.service';
|
||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
@ -40,7 +39,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
mempoolInfoData$: Observable<MempoolInfoData>;
|
mempoolInfoData$: Observable<MempoolInfoData>;
|
||||||
mempoolLoadingStatus$: Observable<number>;
|
mempoolLoadingStatus$: Observable<number>;
|
||||||
vBytesPerSecondLimit = 1667;
|
vBytesPerSecondLimit = 1667;
|
||||||
blocks$: Observable<Block[]>;
|
blocks$: Observable<BlockExtended[]>;
|
||||||
transactions$: Observable<TransactionStripped[]>;
|
transactions$: Observable<TransactionStripped[]>;
|
||||||
latestBlockHeight: number;
|
latestBlockHeight: number;
|
||||||
mempoolTransactionsWeightPerSecondData: any;
|
mempoolTransactionsWeightPerSecondData: any;
|
||||||
@ -199,7 +198,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByBlock(index: number, block: Block) {
|
trackByBlock(index: number, block: BlockExtended) {
|
||||||
return block.height;
|
return block.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,14 +107,6 @@ export interface Block {
|
|||||||
size: number;
|
size: number;
|
||||||
weight: number;
|
weight: number;
|
||||||
previousblockhash: string;
|
previousblockhash: string;
|
||||||
|
|
||||||
// Custom properties
|
|
||||||
medianFee?: number;
|
|
||||||
feeRange?: number[];
|
|
||||||
reward?: number;
|
|
||||||
coinbaseTx?: Transaction;
|
|
||||||
matchRate: number;
|
|
||||||
stage: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Address {
|
export interface Address {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Block, Transaction } from "./electrs.interface";
|
||||||
|
|
||||||
export interface OptimizedMempoolStats {
|
export interface OptimizedMempoolStats {
|
||||||
added: number;
|
added: number;
|
||||||
vbytes_per_second: number;
|
vbytes_per_second: number;
|
||||||
@ -80,3 +82,17 @@ export interface MiningStats {
|
|||||||
pools: SinglePoolStats[],
|
pools: SinglePoolStats[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlockExtension {
|
||||||
|
medianFee?: number;
|
||||||
|
feeRange?: number[];
|
||||||
|
reward?: number;
|
||||||
|
coinbaseTx?: Transaction;
|
||||||
|
matchRate?: number;
|
||||||
|
|
||||||
|
stage?: number; // Frontend only
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockExtended extends Block {
|
||||||
|
extras?: BlockExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { ILoadingIndicators } from '../services/state.service';
|
import { ILoadingIndicators } from '../services/state.service';
|
||||||
import { Block, Transaction } from './electrs.interface';
|
import { Transaction } from './electrs.interface';
|
||||||
|
import { BlockExtended } from './node-api.interface';
|
||||||
|
|
||||||
export interface WebsocketResponse {
|
export interface WebsocketResponse {
|
||||||
block?: Block;
|
block?: BlockExtended;
|
||||||
blocks?: Block[];
|
blocks?: BlockExtended[];
|
||||||
conversions?: any;
|
conversions?: any;
|
||||||
txConfirmed?: boolean;
|
txConfirmed?: boolean;
|
||||||
historicalDate?: string;
|
historicalDate?: string;
|
||||||
|
@ -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$
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Block, Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
|
import { Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
|
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -28,12 +29,12 @@ export class ElectrsApiService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlock$(hash: string): Observable<Block> {
|
getBlock$(hash: string): Observable<BlockExtended> {
|
||||||
return this.httpClient.get<Block>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash);
|
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
listBlocks$(height?: number): Observable<Block[]> {
|
listBlocks$(height?: number): Observable<BlockExtended[]> {
|
||||||
return this.httpClient.get<Block[]>(this.apiBaseUrl + this.apiBasePath + '/api/blocks/' + (height || ''));
|
return this.httpClient.get<BlockExtended[]>(this.apiBaseUrl + this.apiBasePath + '/api/blocks/' + (height || ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransaction$(txId: string): Observable<Transaction> {
|
getTransaction$(txId: string): Observable<Transaction> {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
||||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { IBackendInfo, MempoolBlock, MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { IBackendInfo, MempoolBlock, MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
import { map, shareReplay } from 'rxjs/operators';
|
import { map, shareReplay } from 'rxjs/operators';
|
||||||
@ -72,7 +72,7 @@ export class StateService {
|
|||||||
latestBlockHeight = 0;
|
latestBlockHeight = 0;
|
||||||
|
|
||||||
networkChanged$ = new ReplaySubject<string>(1);
|
networkChanged$ = new ReplaySubject<string>(1);
|
||||||
blocks$: ReplaySubject<[Block, boolean]>;
|
blocks$: ReplaySubject<[BlockExtended, boolean]>;
|
||||||
transactions$ = new ReplaySubject<TransactionStripped>(6);
|
transactions$ = new ReplaySubject<TransactionStripped>(6);
|
||||||
conversions$ = new ReplaySubject<any>(1);
|
conversions$ = new ReplaySubject<any>(1);
|
||||||
bsqPrice$ = new ReplaySubject<number>(1);
|
bsqPrice$ = new ReplaySubject<number>(1);
|
||||||
@ -122,7 +122,7 @@ export class StateService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.blocks$ = new ReplaySubject<[Block, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
this.blocks$ = new ReplaySubject<[BlockExtended, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
||||||
|
|
||||||
if (this.env.BASE_MODULE === 'bisq') {
|
if (this.env.BASE_MODULE === 'bisq') {
|
||||||
this.network = this.env.BASE_MODULE;
|
this.network = this.env.BASE_MODULE;
|
||||||
|
@ -2,11 +2,12 @@ import { Injectable } from '@angular/core';
|
|||||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||||
import { WebsocketResponse, IBackendInfo } from '../interfaces/websocket.interface';
|
import { WebsocketResponse, IBackendInfo } from '../interfaces/websocket.interface';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
||||||
|
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||||
|
|
||||||
const OFFLINE_RETRY_AFTER_MS = 10000;
|
const OFFLINE_RETRY_AFTER_MS = 10000;
|
||||||
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
||||||
@ -207,7 +208,7 @@ export class WebsocketService {
|
|||||||
handleResponse(response: WebsocketResponse) {
|
handleResponse(response: WebsocketResponse) {
|
||||||
if (response.blocks && response.blocks.length) {
|
if (response.blocks && response.blocks.length) {
|
||||||
const blocks = response.blocks;
|
const blocks = response.blocks;
|
||||||
blocks.forEach((block: Block) => {
|
blocks.forEach((block: BlockExtended) => {
|
||||||
if (block.height > this.stateService.latestBlockHeight) {
|
if (block.height > this.stateService.latestBlockHeight) {
|
||||||
this.stateService.latestBlockHeight = block.height;
|
this.stateService.latestBlockHeight = block.height;
|
||||||
this.stateService.blocks$.next([block, false]);
|
this.stateService.blocks$.next([block, false]);
|
||||||
|
@ -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…
Reference in New Issue
Block a user