Compare commits

..

1 Commits

Author SHA1 Message Date
wiz
b3e47e1438 Exempt localhost and local networks from rate limits in nginx.conf
A user recently reported they were rate limiting from querying their own
node - this PR exempts all LAN addresses from rate limits

Needs testing on Umbrel / RaspiBlitz / MyNode before merging
2021-10-15 12:36:25 +09:00
248 changed files with 75633 additions and 81359 deletions

View File

@@ -1,14 +1,7 @@
---
name: 🐛 Bug Report
about: Report bugs or other issues to us on GitHub
---
<!--
SUPPORT REQUESTS:
This is for reporting bugs in Mempool, not for support requests.
If you have a support request, please join our Keybase or Matrix:
SUPPORT REQUESTS: This is for reporting bugs in Mempool.
If you have a support request, please join our Keybase group:
https://keybase.io/team/mempool
https://matrix.to/#/#mempool:bitcoin.kyoto
-->
### Description
@@ -21,11 +14,11 @@ about: Report bugs or other issues to us on GitHub
### Steps to reproduce
<!-- if you can reliably reproduce the bug, list the steps here -->
<!--if you can reliably reproduce the bug, list the steps here -->
### Expected behaviour
<!-- description of the expected behavior -->
<!--description of the expected behavior -->
### Actual behaviour
@@ -33,7 +26,7 @@ about: Report bugs or other issues to us on GitHub
### Screenshots
<!-- Screenshots if gui related, drag and drop to add to the issue -->
<!--Screenshots if gui related, drag and drop to add to the issue -->
#### Device or machine

View File

@@ -1,28 +0,0 @@
---
name: ✨ Feature Request
about: Request a feature or suggest other enhancements 💡
---
<!--
SUPPORT REQUESTS:
This is for requesting features in Mempool, not for support requests.
If you have a support request, please join our Keybase or Matrix:
https://keybase.io/team/mempool
https://matrix.to/#/#mempool:bitcoin.kyoto
-->
### Description
<!-- brief description of the feature request -->
### Problem to be solved
<!-- description of the the problem you're having -->
### Proposed solution
<!-- explain how you think we should solve the problem -->
#### Additional info
<!-- Additional information useful for implementing (e.g. docs, links, etc.) -->

View File

@@ -1,8 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Need help? Chat with us on Matrix
url: https://matrix.to/#/#mempool:bitcoin.kyoto
about: For support requests or general questions
- name: 💬 Need help? Chat with us on Keybase
url: https://keybase.io/team/mempool
about: For support requests or general questions

View File

@@ -15,12 +15,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 16.10.0
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: ${{ matrix.browser }} browser tests (Mempool)
uses: cypress-io/github-action@v2
with:
@@ -31,6 +25,7 @@ jobs:
wait-on-timeout: 120
record: true
parallel: true
env: BASE_MODULE=mempool
group: Tests on ${{ matrix.browser }} (Mempool)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
@@ -51,6 +46,7 @@ jobs:
record: true
parallel: true
spec: cypress/integration/liquid/liquid.spec.ts
env: BASE_MODULE=liquid
group: Tests on ${{ matrix.browser }} (Liquid)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
@@ -71,6 +67,7 @@ jobs:
record: true
parallel: true
spec: cypress/integration/bisq/bisq.spec.ts
env: BASE_MODULE=bisq
group: Tests on ${{ matrix.browser }} (Bisq)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'

1
.nvmrc
View File

@@ -1 +0,0 @@
v16.10.0

View File

@@ -25,7 +25,8 @@ help:
.PHONY: init
init:
@echo ''
mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/data
mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/db-scripts $(DATA)/mysql/data
install -v mariadb-structure.sql $(DATA)/mysql/db-scripts
#REF: https://github.com/mempool/mempool/blob/master/docker/README.md
cat docker/docker-compose.yml > docker-compose.yml
cat backend/mempool-config.sample.json > backend/mempool-config.json

277
README.md
View File

@@ -1,8 +1,8 @@
# The Mempool Open Source Project
# The Mempool Open Source Project
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](https://mempool.space/resources/screenshots/v2.3.0-dashboard.png)
![mempool](https://mempool.space/resources/screenshots/v2.2.1-dashboard.png)
## Installation Methods
@@ -14,249 +14,17 @@ Mempool can be self-hosted on a wide variety of your own hardware, ranging from
4) [Production installation on a powerful FreeBSD server](https://github.com/mempool/mempool/tree/master/production)
5) [High Availability cluster using powerful FreeBSD servers](https://github.com/mempool/mempool/tree/master/production#high-availability)
# Docker Installation
The `docker` directory contains the Dockerfiles used to build and release the official images and a `docker-compose.yml` file that is intended for end users to run a Mempool instance with minimal effort.
## bitcoind only configuration
To run an instance with the default settings, use the following command:
```bash
$ docker-compose up
```
The default configuration will allow you to run Mempool using `bitcoind` as the backend, so address lookups will be disabled. It assumes you have added RPC credentials for the `mempool` user with a `mempool` password in your `bitcoin.conf` file:
```
rpcuser=mempool
rpcpassword=mempool
```
If you want to use your current credentials, update them in the `docker-compose.yml` file:
```
api:
environment:
MEMPOOL_BACKEND: "none"
RPC_HOST: "172.27.0.1"
RPC_PORT: "8332"
RPC_USER: "mempool"
RPC_PASS: "mempool"
```
Note: the IP in the example above refers to Docker's default gateway IP address so the container can hit the `bitcoind` instance running on the host machine. If your setup is different, update it accordingly.
You can check if the instance is running by visiting http://localhost - the graphs will be populated as new transactions are detected.
## bitcoind+electrum configuration
In order to run with a `electrum` compatible server as the backend, in addition to the settings required for running with `bitcoind` above, you will need to make the following changes to the `docker-compose.yml` file:
- Under the `api` service, change the value of the `MEMPOOL_BACKEND` key from `none` to `electrum`:
```
api:
environment:
MEMPOOL_BACKEND: "none"
```
- Under the `api` service, set the `ELECTRUM_HOST` and `ELECTRUM_PORT` keys to your Docker host IP address and set `ELECTRUM_TLS_ENABLED` to `false`:
```
api:
environment:
ELECTRUM_HOST: "172.27.0.1"
ELECTRUM_PORT: "50002"
ELECTRUM_TLS_ENABLED: "false"
```
You can update any of the backend settings in the `mempool-config.json` file using the following environment variables to override them under the same `api` `environment` section.
JSON:
```
"MEMPOOL": {
"NETWORK": "mainnet",
"BACKEND": "electrum",
"HTTP_PORT": 8999,
"SPAWN_CLUSTER_PROCS": 0,
"API_URL_PREFIX": "/api/v1/",
"POLL_RATE_MS": 2000,
"CACHE_DIR": "./cache",
"CLEAR_PROTECTION_MINUTES": 20,
"RECOMMENDED_FEE_PERCENTILE": 50,
"BLOCK_WEIGHT_UNITS": 4000000,
"INITIAL_BLOCKS_AMOUNT": 8,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"PRICE_FEED_UPDATE_INTERVAL": 3600,
"USE_SECOND_NODE_FOR_MINFEE": false,
"EXTERNAL_ASSETS": []
},
```
docker-compose overrides::
```
MEMPOOL_NETWORK: ""
MEMPOOL_BACKEND: ""
MEMPOOL_HTTP_PORT: ""
MEMPOOL_SPAWN_CLUSTER_PROCS: ""
MEMPOOL_API_URL_PREFIX: ""
MEMPOOL_POLL_RATE_MS: ""
MEMPOOL_CACHE_DIR: ""
MEMPOOL_CLEAR_PROTECTION_MINUTES: ""
MEMPOOL_RECOMMENDED_FEE_PERCENTILE: ""
MEMPOOL_BLOCK_WEIGHT_UNITS: ""
MEMPOOL_INITIAL_BLOCKS_AMOUNT: ""
MEMPOOL_MEMPOOL_BLOCKS_AMOUNT: ""
MEMPOOL_PRICE_FEED_UPDATE_INTERVAL: ""
MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: ""
MEMPOOL_EXTERNAL_ASSETS: ""
```
JSON:
```
"CORE_RPC": {
"HOST": "127.0.0.1",
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "mempool"
},
```
docker-compose overrides:
```
CORE_RPC_HOST: ""
CORE_RPC_PORT: ""
CORE_RPC_USERNAME: ""
CORE_RPC_PASSWORD: ""
```
JSON:
```
"ELECTRUM": {
"HOST": "127.0.0.1",
"PORT": 50002,
"TLS_ENABLED": true
},
```
docker-compose overrides:
```
ELECTRUM_HOST: ""
ELECTRUM_PORT: ""
ELECTRUM_TLS: ""
```
JSON:
```
"ESPLORA": {
"REST_API_URL": "http://127.0.0.1:3000"
},
```
docker-compose overrides:
```
ESPLORA_REST_API_URL: ""
```
JSON:
```
"SECOND_CORE_RPC": {
"HOST": "127.0.0.1",
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "mempool"
},
```
docker-compose overrides:
```
SECOND_CORE_RPC_HOST: ""
SECOND_CORE_RPC_PORT: ""
SECOND_CORE_RPC_USERNAME: ""
SECOND_CORE_RPC_PASSWORD: ""
```
JSON:
```
"DATABASE": {
"ENABLED": true,
"HOST": "127.0.0.1",
"PORT": 3306,
"DATABASE": "mempool",
"USERNAME": "mempool",
"PASSWORD": "mempool"
},
```
docker-compose overrides:
```
DATABASE_ENABLED: ""
DATABASE_HOST: ""
DATABASE_PORT: ""
DATABASE_DATABASE: ""
DATABASE_USERAME: ""
DATABASE_PASSWORD: ""
```
JSON:
```
"SYSLOG": {
"ENABLED": true,
"HOST": "127.0.0.1",
"PORT": 514,
"MIN_PRIORITY": "info",
"FACILITY": "local7"
},
```
docker-compose overrides:
```
SYSLOG_ENABLED: ""
SYSLOG_HOST: ""
SYSLOG_PORT: ""
SYSLOG_MIN_PRIORITY: ""
SYSLOG_FACILITY: ""
```
JSON:
```
"STATISTICS": {
"ENABLED": true,
"TX_PER_SECOND_SAMPLE_PERIOD": 150
},
```
docker-compose overrides:
```
STATISTICS_ENABLED: ""
STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD: ""
```
JSON:
```
"BISQ": {
"ENABLED": false,
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
}
```
docker-compose overrides:
```
BISQ_ENABLED: ""
BISQ_DATA_PATH: ""
```
# Manual Installation
The following instructions are for a manual installation on Linux or FreeBSD. The file and directory paths may need to be changed to match your OS.
## Dependencies
* [Bitcoin](https://github.com/bitcoin/bitcoin)
* [Electrum](https://github.com/romanz/electrs)
* [NodeJS](https://github.com/nodejs/node)
* [MariaDB](https://github.com/mariadb/server)
* [Nginx](https://github.com/nginx/nginx)
* Bitcoin Core (no pruning, txindex=1)
* Electrum Server (romanz/electrs)
* NodeJS (official stable LTS)
* MariaDB (default config)
* Nginx (use supplied nginx.conf and nginx-mempool.conf)
## Mempool
@@ -273,7 +41,7 @@ Clone the mempool repo, and checkout the latest release tag:
Enable RPC and txindex in `bitcoin.conf`:
```bash
rpcuser=mempool
rpcpassword=mempool
rpcpassword=71b61986da5b03a5694d7c7d5165ece5
txindex=1
```
@@ -286,7 +54,7 @@ Install MariaDB from OS package manager:
# macOS
brew install mariadb
mysql.server start
brew services start mariadb
```
Create database and grant privileges:
@@ -301,13 +69,18 @@ Create database and grant privileges:
Query OK, 0 rows affected (0.00 sec)
```
From the mempool repo's top-level folder, import the database structure:
```bash
mysql -u mempool -p mempool < mariadb-structure.sql
```
## Mempool Backend
Install mempool dependencies from npm and build the backend:
```bash
# backend
cd backend
npm install --prod
npm install
npm run build
```
@@ -323,18 +96,18 @@ Edit `mempool-config.json` to add your Bitcoin Core node RPC credentials:
"MEMPOOL": {
"NETWORK": "mainnet",
"BACKEND": "electrum",
"HTTP_PORT": 8999
"HTTP_PORT": 8999,
"API_URL_PREFIX": "/api/v1/",
"POLL_RATE_MS": 2000
},
"CORE_RPC": {
"HOST": "127.0.0.1",
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "mempool"
"PASSWORD": "71b61986da5b03a5694d7c7d5165ece5"
},
"ELECTRUM": {
"HOST": "127.0.0.1",
"PORT": 50002,
"TLS_ENABLED": true
"TLS_ENABLED": true,
},
"DATABASE": {
"ENABLED": true,
@@ -343,6 +116,10 @@ Edit `mempool-config.json` to add your Bitcoin Core node RPC credentials:
"USERNAME": "mempool",
"PASSWORD": "mempool",
"DATABASE": "mempool"
},
"STATISTICS": {
"ENABLED": true,
"TX_PER_SECOND_SAMPLE_PERIOD": 150
}
}
```
@@ -383,14 +160,14 @@ Install mempool dependencies from npm and build the frontend static HTML/CSS/JS:
```bash
# frontend
cd frontend
npm install --prod
npm install
npm run build
```
Install the output into nginx webroot folder:
```bash
sudo rsync -av --delete dist/ /var/www/
sudo rsync -av --delete dist/mempool /var/www/
```
## nginx + certbot
@@ -399,7 +176,7 @@ Install the supplied nginx.conf and nginx-mempool.conf in /etc/nginx
```bash
# install nginx and certbot
apt-get install -y nginx python3-certbot-nginx
apt-get install -y nginx python-certbot-nginx
# install the mempool configuration for nginx
cp nginx.conf nginx-mempool.conf /etc/nginx/

7
backend/.gitignore vendored
View File

@@ -1,10 +1,7 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# production config and external assets
*.json
!mempool-config.sample.json
icons.json
# production config
mempool-config.json
# compiled output
/dist

View File

@@ -13,8 +13,7 @@
"INITIAL_BLOCKS_AMOUNT": 8,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"PRICE_FEED_UPDATE_INTERVAL": 3600,
"USE_SECOND_NODE_FOR_MINFEE": false,
"EXTERNAL_ASSETS": []
"USE_SECOND_NODE_FOR_MINFEE": false
},
"CORE_RPC": {
"HOST": "127.0.0.1",

View File

@@ -11,22 +11,22 @@
"dependencies": {
"@mempool/bitcoin": "^3.0.3",
"@mempool/electrum-client": "^1.1.7",
"@types/ws": "8.2.2",
"axios": "0.24.0",
"bitcoinjs-lib": "6.0.1",
"axios": "^0.21.1",
"bitcoinjs-lib": "^5.2.0",
"crypto-js": "^4.0.0",
"express": "^4.17.1",
"locutus": "^2.0.12",
"mysql2": "2.3.3",
"mysql2": "2.2.5",
"node-worker-threads-pool": "^1.4.3",
"typescript": "4.4.4",
"ws": "8.3.0"
"ws": "^7.4.6"
},
"devDependencies": {
"@types/compression": "^1.0.1",
"@types/express": "^4.17.2",
"@types/locutus": "^0.0.6",
"tslint": "^6.1.0"
"@types/ws": "^7.4.4",
"tslint": "^6.1.0",
"typescript": "4.4.2"
}
},
"node_modules/@babel/code-frame": {
@@ -137,7 +137,8 @@
"node_modules/@types/node": {
"version": "14.14.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz",
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A=="
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==",
"dev": true
},
"node_modules/@types/qs": {
"version": "6.9.5",
@@ -162,9 +163,11 @@
}
},
"node_modules/@types/ws": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
"integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
@@ -208,11 +211,11 @@
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"node_modules/axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"dependencies": {
"follow-redirects": "^1.14.4"
"follow-redirects": "^1.10.0"
}
},
"node_modules/balanced-match": {
@@ -222,17 +225,25 @@
"dev": true
},
"node_modules/base-x": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
"integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/bech32": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bip174": {
"version": "2.0.1",
@@ -242,23 +253,71 @@
"node": ">=8.0.0"
}
},
"node_modules/bitcoinjs-lib": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz",
"integrity": "sha512-x/7D4jDj/MMkmO6t3p2CSDXTqpwZ/jRsRiJDmaiXabrR9XRo7jwby8HRn7EyK1h24rKFFI7vI0ay4czl6bDOZQ==",
"node_modules/bip32": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
"dependencies": {
"bech32": "^2.0.0",
"@types/node": "10.12.18",
"bs58check": "^2.1.1",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
"tiny-secp256k1": "^1.1.3",
"typeforce": "^1.11.5",
"wif": "^2.0.6"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/bip32/node_modules/@types/node": {
"version": "10.12.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
},
"node_modules/bip66": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/bitcoin-ops": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
},
"node_modules/bitcoinjs-lib": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz",
"integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==",
"dependencies": {
"bech32": "^1.1.2",
"bip174": "^2.0.1",
"bs58check": "^2.1.2",
"bip32": "^2.0.4",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.4.0",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "^1.0.1",
"randombytes": "^2.0.1",
"tiny-secp256k1": "^1.1.1",
"typeforce": "^1.11.3",
"varuint-bitcoin": "^1.1.2",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
},
"node_modules/body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@@ -289,6 +348,11 @@
"concat-map": "0.0.1"
}
},
"node_modules/brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
},
"node_modules/bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
@@ -423,6 +487,19 @@
"sha.js": "^2.4.0"
}
},
"node_modules/create-hmac": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"dependencies": {
"cipher-base": "^1.0.3",
"create-hash": "^1.1.0",
"inherits": "^2.0.1",
"ripemd160": "^2.0.0",
"safe-buffer": "^5.0.1",
"sha.js": "^2.4.8"
}
},
"node_modules/crypto-js": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
@@ -437,9 +514,9 @@
}
},
"node_modules/denque": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
"integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
"integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==",
"engines": {
"node": ">=0.10"
}
@@ -471,6 +548,20 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"node_modules/elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dependencies": {
"bn.js": "^4.11.9",
"brorand": "^1.1.0",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.1",
"inherits": "^2.0.4",
"minimalistic-assert": "^1.0.1",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -559,6 +650,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -577,9 +673,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.14.7",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==",
"funding": [
{
"type": "individual",
@@ -685,6 +781,25 @@
"node": ">=4"
}
},
"node_modules/hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dependencies": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"node_modules/hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dependencies": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"node_modules/http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
@@ -822,6 +937,11 @@
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"node_modules/merkle-lib": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz",
"integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY="
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -860,6 +980,16 @@
"node": ">= 0.6"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"node_modules/minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -896,13 +1026,13 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/mysql2": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz",
"integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==",
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz",
"integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==",
"dependencies": {
"denque": "^2.0.1",
"denque": "^1.4.1",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"iconv-lite": "^0.6.2",
"long": "^4.0.0",
"lru-cache": "^6.0.0",
"named-placeholders": "^1.1.2",
@@ -914,9 +1044,9 @@
}
},
"node_modules/mysql2/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
@@ -949,6 +1079,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
},
"node_modules/nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
},
"node_modules/negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -1000,9 +1135,9 @@
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"node_modules/path-to-regexp": {
@@ -1027,6 +1162,14 @@
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"node_modules/pushdata-bitcoin": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
"dependencies": {
"bitcoin-ops": "^1.3.0"
}
},
"node_modules/qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -1035,6 +1178,14 @@
"node": ">=0.6"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -1231,6 +1382,22 @@
"node": ">=4"
}
},
"node_modules/tiny-secp256k1": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
"hasInstallScript": true,
"dependencies": {
"bindings": "^1.3.0",
"bn.js": "^4.11.8",
"create-hmac": "^1.1.7",
"elliptic": "^6.4.0",
"nan": "^2.13.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
@@ -1306,9 +1473,11 @@
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
},
"node_modules/typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
"integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -1369,11 +1538,11 @@
"dev": true
},
"node_modules/ws": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"engines": {
"node": ">=10.0.0"
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
@@ -1497,7 +1666,8 @@
"@types/node": {
"version": "14.14.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz",
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A=="
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==",
"dev": true
},
"@types/qs": {
"version": "6.9.5",
@@ -1522,9 +1692,10 @@
}
},
"@types/ws": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
"integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
@@ -1562,11 +1733,11 @@
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.14.4"
"follow-redirects": "^1.10.0"
}
},
"balanced-match": {
@@ -1576,37 +1747,92 @@
"dev": true
},
"base-x": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
"integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"bech32": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bip174": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz",
"integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
},
"bitcoinjs-lib": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz",
"integrity": "sha512-x/7D4jDj/MMkmO6t3p2CSDXTqpwZ/jRsRiJDmaiXabrR9XRo7jwby8HRn7EyK1h24rKFFI7vI0ay4czl6bDOZQ==",
"bip32": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
"integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
"requires": {
"bech32": "^2.0.0",
"@types/node": "10.12.18",
"bs58check": "^2.1.1",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
"tiny-secp256k1": "^1.1.3",
"typeforce": "^1.11.5",
"wif": "^2.0.6"
},
"dependencies": {
"@types/node": {
"version": "10.12.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
}
}
},
"bip66": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
"integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"bitcoin-ops": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
},
"bitcoinjs-lib": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz",
"integrity": "sha512-5DcLxGUDejgNBYcieMIUfjORtUeNWl828VWLHJGVKZCb4zIS1oOySTUr0LGmcqJBQgTBz3bGbRQla4FgrdQEIQ==",
"requires": {
"bech32": "^1.1.2",
"bip174": "^2.0.1",
"bs58check": "^2.1.2",
"bip32": "^2.0.4",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.4.0",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "^1.0.1",
"randombytes": "^2.0.1",
"tiny-secp256k1": "^1.1.1",
"typeforce": "^1.11.3",
"varuint-bitcoin": "^1.1.2",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
}
},
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@@ -1634,6 +1860,11 @@
"concat-map": "0.0.1"
}
},
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
},
"bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
@@ -1752,6 +1983,19 @@
"sha.js": "^2.4.0"
}
},
"create-hmac": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"requires": {
"cipher-base": "^1.0.3",
"create-hash": "^1.1.0",
"inherits": "^2.0.1",
"ripemd160": "^2.0.0",
"safe-buffer": "^5.0.1",
"sha.js": "^2.4.8"
}
},
"crypto-js": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
@@ -1766,9 +2010,9 @@
}
},
"denque": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
"integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ=="
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
"integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
},
"depd": {
"version": "1.1.2",
@@ -1791,6 +2035,20 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"requires": {
"bn.js": "^4.11.9",
"brorand": "^1.1.0",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.1",
"inherits": "^2.0.4",
"minimalistic-assert": "^1.0.1",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -1862,6 +2120,11 @@
}
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -1877,9 +2140,9 @@
}
},
"follow-redirects": {
"version": "1.14.7",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
},
"forwarded": {
"version": "0.1.2",
@@ -1950,6 +2213,25 @@
"safe-buffer": "^5.2.0"
}
},
"hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
@@ -2065,6 +2347,11 @@
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"merkle-lib": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz",
"integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -2088,6 +2375,16 @@
"mime-db": "1.45.0"
}
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -2118,13 +2415,13 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mysql2": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz",
"integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==",
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz",
"integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==",
"requires": {
"denque": "^2.0.1",
"denque": "^1.4.1",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"iconv-lite": "^0.6.2",
"long": "^4.0.0",
"lru-cache": "^6.0.0",
"named-placeholders": "^1.1.2",
@@ -2133,9 +2430,9 @@
},
"dependencies": {
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
@@ -2166,6 +2463,11 @@
}
}
},
"nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -2205,9 +2507,9 @@
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-to-regexp": {
@@ -2229,11 +2531,27 @@
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"pushdata-bitcoin": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
"requires": {
"bitcoin-ops": "^1.3.0"
}
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"requires": {
"safe-buffer": "^5.1.0"
}
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -2385,6 +2703,18 @@
"has-flag": "^3.0.0"
}
},
"tiny-secp256k1": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz",
"integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==",
"requires": {
"bindings": "^1.3.0",
"bn.js": "^4.11.8",
"create-hmac": "^1.1.7",
"elliptic": "^6.4.0",
"nan": "^2.13.2"
}
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
@@ -2441,9 +2771,10 @@
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
},
"typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA=="
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
"integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
"dev": true
},
"unpipe": {
"version": "1.0.0",
@@ -2488,9 +2819,9 @@
"dev": true
},
"ws": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"requires": {}
},
"yallist": {

View File

@@ -30,21 +30,21 @@
"dependencies": {
"@mempool/bitcoin": "^3.0.3",
"@mempool/electrum-client": "^1.1.7",
"@types/ws": "8.2.2",
"axios": "0.24.0",
"bitcoinjs-lib": "6.0.1",
"axios": "^0.21.1",
"bitcoinjs-lib": "^5.2.0",
"crypto-js": "^4.0.0",
"express": "^4.17.1",
"locutus": "^2.0.12",
"mysql2": "2.3.3",
"mysql2": "2.2.5",
"node-worker-threads-pool": "^1.4.3",
"typescript": "4.4.4",
"ws": "8.3.0"
"ws": "^7.4.6"
},
"devDependencies": {
"@types/compression": "^1.0.1",
"@types/express": "^4.17.2",
"@types/locutus": "^0.0.6",
"tslint": "^6.1.0"
"@types/ws": "^7.4.4",
"tslint": "^6.1.0",
"typescript": "4.4.2"
}
}

View File

@@ -108,7 +108,7 @@ export namespace IBitcoinApi {
scriptPubKey: string; // (string) The hex-encoded scriptPubKey generated by the address
isscript: boolean; // (boolean) If the key is a script
iswitness: boolean; // (boolean) If the address is a witness
witness_version?: number; // (numeric, optional) The version number of the witness program
witness_version?: boolean; // (numeric, optional) The version number of the witness program
witness_program: string; // (string, optional) The hex value of the witness program
confidential_key?: string; // (string) Elements only
unconfidential?: string; // (string) Elements only

View File

@@ -299,11 +299,6 @@ class BitcoinApi implements AbstractBitcoinApi {
const witnessScript = vin.witness[vin.witness.length - 1];
vin.inner_witnessscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(witnessScript, 'hex')));
}
if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness && vin.witness.length > 1) {
const witnessScript = vin.witness[vin.witness.length - 2];
vin.inner_witnessscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(witnessScript, 'hex')));
}
}
}

View File

@@ -87,8 +87,11 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
},
'electrum': true,
};
} catch (e: any) {
throw new Error(typeof e === 'string' ? e : e && e.message || e);
} catch (e) {
if (e === 'failed to get confirmed status') {
e = 'The number of transactions on this address exceeds the Electrum server limit';
}
throw new Error(typeof e === 'string' ? e : 'Error');
}
}
@@ -121,9 +124,12 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
}
return transactions;
} catch (e: any) {
} catch (e) {
loadingIndicators.setProgress('address-' + address, 100);
throw new Error(typeof e === 'string' ? e : e && e.message || e);
if (e === 'failed to get confirmed status') {
e = 'The number of transactions on this address exceeds the Electrum server limit';
}
throw new Error(typeof e === 'string' ? e : 'Error');
}
}

View File

@@ -1,14 +1,7 @@
import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
import config from '../config';
export class Common {
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
'144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'
: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
static _isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet';
static isLiquid(): boolean {
return this._isLiquid;
}
static nativeAssetId = '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
static median(numbers: number[]) {
let medianNr = 0;
@@ -114,7 +107,7 @@ export class Common {
totalFees += tx.bestDescendant.fee;
}
tx.effectiveFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1, totalFees / (totalWeight / 4));
tx.effectiveFeePerVsize = Math.max(config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1, totalFees / (totalWeight / 4));
tx.cpfpChecked = true;
return {

View File

@@ -1,340 +0,0 @@
import { PoolConnection } from 'mysql2/promise';
import config from '../config';
import { DB } from '../database';
import logger from '../logger';
const sleep = (ms: number) => new Promise( res => setTimeout(res, ms));
class DatabaseMigration {
private static currentVersion = 2;
private queryTimeout = 120000;
private statisticsAddedIndexed = false;
constructor() { }
/**
* Entry point
*/
public async $initializeOrMigrateDatabase(): Promise<void> {
logger.info('MIGRATIONS: Running migrations');
await this.$printDatabaseVersion();
// First of all, if the `state` database does not exist, create it so we can track migration version
if (!await this.$checkIfTableExists('state')) {
logger.info('MIGRATIONS: `state` table does not exist. Creating it.');
try {
await this.$createMigrationStateTable();
} catch (e) {
logger.err('MIGRATIONS: Unable to create `state` table, aborting in 10 seconds. ' + e);
await sleep(10000);
process.exit(-1);
}
logger.info('MIGRATIONS: `state` table initialized.');
}
let databaseSchemaVersion = 0;
try {
databaseSchemaVersion = await this.$getSchemaVersionFromDatabase();
} catch (e) {
logger.err('MIGRATIONS: Unable to get current database migration version, aborting in 10 seconds. ' + e);
await sleep(10000);
process.exit(-1);
}
logger.info('MIGRATIONS: Current state.schema_version ' + databaseSchemaVersion);
logger.info('MIGRATIONS: Latest DatabaseMigration.version is ' + DatabaseMigration.currentVersion);
if (databaseSchemaVersion >= DatabaseMigration.currentVersion) {
logger.info('MIGRATIONS: Nothing to do.');
return;
}
// Now, create missing tables. Those queries cannot be wrapped into a transaction unfortunately
try {
await this.$createMissingTablesAndIndexes(databaseSchemaVersion);
} catch (e) {
logger.err('MIGRATIONS: Unable to create required tables, aborting in 10 seconds. ' + e);
await sleep(10000);
process.exit(-1);
}
if (DatabaseMigration.currentVersion > databaseSchemaVersion) {
logger.info('MIGRATIONS: Upgrading datababse schema');
try {
await this.$migrateTableSchemaFromVersion(databaseSchemaVersion);
logger.info(`MIGRATIONS: OK. Database schema have been migrated from version ${databaseSchemaVersion} to ${DatabaseMigration.currentVersion} (latest version)`);
} catch (e) {
logger.err('MIGRATIONS: Unable to migrate database, aborting. ' + e);
}
}
return;
}
/**
* Create all missing tables
*/
private async $createMissingTablesAndIndexes(databaseSchemaVersion: number) {
await this.$setStatisticsAddedIndexedFlag(databaseSchemaVersion);
const connection = await DB.pool.getConnection();
try {
await this.$executeQuery(connection, this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs'));
await this.$executeQuery(connection, this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) {
await this.$executeQuery(connection, `CREATE INDEX added ON statistics (added);`);
}
connection.release();
} catch (e) {
connection.release();
throw e;
}
}
/**
* Special case here for the `statistics` table - It appeared that somehow some dbs already had the `added` field indexed
* while it does not appear in previous schemas. The mariadb command "CREATE INDEX IF NOT EXISTS" is not supported on
* older mariadb version. Therefore we set a flag here in order to know if the index needs to be created or not before
* running the migration process
*/
private async $setStatisticsAddedIndexedFlag(databaseSchemaVersion: number) {
if (databaseSchemaVersion >= 2) {
this.statisticsAddedIndexed = true;
return;
}
const connection = await DB.pool.getConnection();
try {
// We don't use "CREATE INDEX IF NOT EXISTS" because it is not supported on old mariadb version 5.X
const query = `SELECT COUNT(1) hasIndex FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_schema=DATABASE() AND table_name='statistics' AND index_name='added';`;
const [rows] = await this.$executeQuery(connection, query, true);
if (rows[0].hasIndex === 0) {
logger.info('MIGRATIONS: `statistics.added` is not indexed');
this.statisticsAddedIndexed = false;
} else if (rows[0].hasIndex === 1) {
logger.info('MIGRATIONS: `statistics.added` is already indexed');
this.statisticsAddedIndexed = true;
}
} catch (e) {
// Should really never happen but just in case it fails, we just don't execute
// any query related to this indexing so it won't fail if the index actually already exists
logger.err('MIGRATIONS: Unable to check if `statistics.added` INDEX exist or not.');
this.statisticsAddedIndexed = true;
}
connection.release();
}
/**
* Small query execution wrapper to log all executed queries
*/
private async $executeQuery(connection: PoolConnection, query: string, silent: boolean = false): Promise<any> {
if (!silent) {
logger.info('MIGRATIONS: Execute query:\n' + query);
}
return connection.query<any>({ sql: query, timeout: this.queryTimeout });
}
/**
* Check if 'table' exists in the database
*/
private async $checkIfTableExists(table: string): Promise<boolean> {
const connection = await DB.pool.getConnection();
const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return rows[0]['COUNT(*)'] === 1;
}
/**
* Get current database version
*/
private async $getSchemaVersionFromDatabase(): Promise<number> {
const connection = await DB.pool.getConnection();
const query = `SELECT number FROM state WHERE name = 'schema_version';`;
const [rows] = await this.$executeQuery(connection, query, true);
connection.release();
return rows[0]['number'];
}
/**
* Create the `state` table
*/
private async $createMigrationStateTable(): Promise<void> {
const connection = await DB.pool.getConnection();
try {
const query = `CREATE TABLE IF NOT EXISTS state (
name varchar(25) NOT NULL,
number int(11) NULL,
string varchar(100) NULL,
CONSTRAINT name_unique UNIQUE (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
await this.$executeQuery(connection, query);
// Set initial values
await this.$executeQuery(connection, `INSERT INTO state VALUES('schema_version', 0, NULL);`);
await this.$executeQuery(connection, `INSERT INTO state VALUES('last_elements_block', 0, NULL);`);
connection.release();
} catch (e) {
connection.release();
throw e;
}
}
/**
* We actually execute the migrations queries here
*/
private async $migrateTableSchemaFromVersion(version: number): Promise<void> {
const transactionQueries: string[] = [];
for (const query of this.getMigrationQueriesFromVersion(version)) {
transactionQueries.push(query);
}
transactionQueries.push(this.getUpdateToLatestSchemaVersionQuery());
const connection = await DB.pool.getConnection();
try {
await this.$executeQuery(connection, 'START TRANSACTION;');
await this.$executeQuery(connection, 'SET autocommit = 0;');
for (const query of transactionQueries) {
await this.$executeQuery(connection, query);
}
await this.$executeQuery(connection, 'COMMIT;');
connection.release();
} catch (e) {
await this.$executeQuery(connection, 'ROLLBACK;');
connection.release();
throw e;
}
}
/**
* Generate migration queries based on schema version
*/
private getMigrationQueriesFromVersion(version: number): string[] {
const queries: string[] = [];
if (version < 1) {
if (config.MEMPOOL.NETWORK !== 'liquid' && config.MEMPOOL.NETWORK !== 'liquidtestnet') {
queries.push(this.getShiftStatisticsQuery());
}
}
return queries;
}
/**
* Save the schema version in the database
*/
private getUpdateToLatestSchemaVersionQuery(): string {
return `UPDATE state SET number = ${DatabaseMigration.currentVersion} WHERE name = 'schema_version';`;
}
/**
* Print current database version
*/
private async $printDatabaseVersion() {
const connection = await DB.pool.getConnection();
try {
const [rows] = await this.$executeQuery(connection, 'SELECT VERSION() as version;', true);
logger.info(`MIGRATIONS: Database engine version '${rows[0].version}'`);
} catch (e) {
logger.info(`MIGRATIONS: Could not fetch database engine version. ` + e);
}
connection.release();
}
// Couple of wrappers to clean the main logic
private getShiftStatisticsQuery(): string {
return `UPDATE statistics SET
vsize_1 = vsize_1 + vsize_2, vsize_2 = vsize_3,
vsize_3 = vsize_4, vsize_4 = vsize_5,
vsize_5 = vsize_6, vsize_6 = vsize_8,
vsize_8 = vsize_10, vsize_10 = vsize_12,
vsize_12 = vsize_15, vsize_15 = vsize_20,
vsize_20 = vsize_30, vsize_30 = vsize_40,
vsize_40 = vsize_50, vsize_50 = vsize_60,
vsize_60 = vsize_70, vsize_70 = vsize_80,
vsize_80 = vsize_90, vsize_90 = vsize_100,
vsize_100 = vsize_125, vsize_125 = vsize_150,
vsize_150 = vsize_175, vsize_175 = vsize_200,
vsize_200 = vsize_250, vsize_250 = vsize_300,
vsize_300 = vsize_350, vsize_350 = vsize_400,
vsize_400 = vsize_500, vsize_500 = vsize_600,
vsize_600 = vsize_700, vsize_700 = vsize_800,
vsize_800 = vsize_900, vsize_900 = vsize_1000,
vsize_1000 = vsize_1200, vsize_1200 = vsize_1400,
vsize_1400 = vsize_1800, vsize_1800 = vsize_2000, vsize_2000 = 0;`;
}
private getCreateStatisticsQuery(): string {
return `CREATE TABLE IF NOT EXISTS statistics (
id int(11) NOT NULL AUTO_INCREMENT,
added datetime NOT NULL,
unconfirmed_transactions int(11) UNSIGNED NOT NULL,
tx_per_second float UNSIGNED NOT NULL,
vbytes_per_second int(10) UNSIGNED NOT NULL,
mempool_byte_weight int(10) UNSIGNED NOT NULL,
fee_data longtext NOT NULL,
total_fee double UNSIGNED NOT NULL,
vsize_1 int(11) NOT NULL,
vsize_2 int(11) NOT NULL,
vsize_3 int(11) NOT NULL,
vsize_4 int(11) NOT NULL,
vsize_5 int(11) NOT NULL,
vsize_6 int(11) NOT NULL,
vsize_8 int(11) NOT NULL,
vsize_10 int(11) NOT NULL,
vsize_12 int(11) NOT NULL,
vsize_15 int(11) NOT NULL,
vsize_20 int(11) NOT NULL,
vsize_30 int(11) NOT NULL,
vsize_40 int(11) NOT NULL,
vsize_50 int(11) NOT NULL,
vsize_60 int(11) NOT NULL,
vsize_70 int(11) NOT NULL,
vsize_80 int(11) NOT NULL,
vsize_90 int(11) NOT NULL,
vsize_100 int(11) NOT NULL,
vsize_125 int(11) NOT NULL,
vsize_150 int(11) NOT NULL,
vsize_175 int(11) NOT NULL,
vsize_200 int(11) NOT NULL,
vsize_250 int(11) NOT NULL,
vsize_300 int(11) NOT NULL,
vsize_350 int(11) NOT NULL,
vsize_400 int(11) NOT NULL,
vsize_500 int(11) NOT NULL,
vsize_600 int(11) NOT NULL,
vsize_700 int(11) NOT NULL,
vsize_800 int(11) NOT NULL,
vsize_900 int(11) NOT NULL,
vsize_1000 int(11) NOT NULL,
vsize_1200 int(11) NOT NULL,
vsize_1400 int(11) NOT NULL,
vsize_1600 int(11) NOT NULL,
vsize_1800 int(11) NOT NULL,
vsize_2000 int(11) NOT NULL,
CONSTRAINT PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
}
private getCreateElementsTableQuery(): string {
return `CREATE TABLE IF NOT EXISTS elements_pegs (
block int(11) NOT NULL,
datetime int(11) NOT NULL,
amount bigint(20) NOT NULL,
txid varchar(65) NOT NULL,
txindex int(11) NOT NULL,
bitcoinaddress varchar(100) NOT NULL,
bitcointxid varchar(65) NOT NULL,
bitcoinindex int(11) NOT NULL,
final_tx int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
}
}
export default new DatabaseMigration();

View File

@@ -1,13 +1,12 @@
import config from '../config';
import { MempoolBlock } from '../mempool.interfaces';
import { Common } from './common';
import mempool from './mempool';
import projectedBlocks from './mempool-blocks';
class FeeApi {
constructor() { }
defaultFee = Common.isLiquid() ? 0.1 : 1;
defaultFee = config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1;
public getRecommendedFee() {
const pBlocks = projectedBlocks.getMempoolBlocks();

View File

@@ -18,12 +18,12 @@ class ElementsParser {
this.isRunning = true;
const result = await bitcoinClient.getChainTips();
const tip = result[0].height;
const latestBlockHeight = await this.$getLatestBlockHeightFromDatabase();
for (let height = latestBlockHeight + 1; height <= tip; height++) {
const latestBlock = await this.$getLatestBlockFromDatabase();
for (let height = latestBlock.block + 1; height <= tip; height++) {
const blockHash: IBitcoinApi.ChainTips = await bitcoinClient.getBlockHash(height);
const block: IBitcoinApi.Block = await bitcoinClient.getBlock(blockHash, 2);
await this.$parseBlock(block);
await this.$saveLatestBlockToDatabase(block.height);
await this.$saveLatestBlockToDatabase(block.height, block.time, block.hash);
}
this.isRunning = false;
} catch (e) {
@@ -92,18 +92,18 @@ class ElementsParser {
logger.debug(`Saved L-BTC peg from block height #${height} with TXID ${txid}.`);
}
protected async $getLatestBlockHeightFromDatabase(): Promise<number> {
protected async $getLatestBlockFromDatabase(): Promise<any> {
const connection = await DB.pool.getConnection();
const query = `SELECT number FROM state WHERE name = 'last_elements_block'`;
const query = `SELECT block, datetime, block_hash FROM last_elements_block`;
const [rows] = await connection.query<any>(query);
connection.release();
return rows[0]['number'];
return rows[0];
}
protected async $saveLatestBlockToDatabase(blockHeight: number) {
protected async $saveLatestBlockToDatabase(blockHeight: number, datetime: number, blockHash: string) {
const connection = await DB.pool.getConnection();
const query = `UPDATE state SET number = ? WHERE name = 'last_elements_block'`;
await connection.query<any>(query, [blockHeight]);
const query = `UPDATE last_elements_block SET block = ?, datetime = ?, block_hash = ?`;
await connection.query<any>(query, [blockHeight, datetime, blockHash]);
connection.release();
}
}

View File

@@ -1,39 +0,0 @@
import * as fs from 'fs';
import config from '../../config';
import logger from '../../logger';
class Icons {
private static FILE_NAME = './icons.json';
private iconIds: string[] = [];
private icons: { [assetId: string]: string; } = {};
constructor() {}
public loadIcons() {
if (!fs.existsSync(Icons.FILE_NAME)) {
logger.warn(`${Icons.FILE_NAME} does not exist. No Liquid icons loaded.`);
return;
}
const cacheData = fs.readFileSync(Icons.FILE_NAME, 'utf8');
this.icons = JSON.parse(cacheData);
for (const i in this.icons) {
this.iconIds.push(i);
}
logger.debug(`Liquid icons has been loaded.`);
}
public getIconByAssetId(assetId: string): Buffer | undefined {
const icon = this.icons[assetId];
if (icon) {
return Buffer.from(icon, 'base64');
}
}
public getAllIconIds() {
return this.iconIds;
}
}
export default new Icons();

View File

@@ -3,13 +3,14 @@ import { DB } from '../database';
import logger from '../logger';
import { Statistic, TransactionExtended, OptimizedStatistic } from '../mempool.interfaces';
import config from '../config';
import { Common } from './common';
class Statistics {
protected intervalTimer: NodeJS.Timer | undefined;
protected newStatisticsEntryCallback: ((stats: OptimizedStatistic) => void) | undefined;
protected queryTimeout = 120000;
protected cache: { [date: string]: OptimizedStatistic[] } = {
'24h': [], '1w': [], '1m': [], '3m': [], '6m': [], '1y': [],
};
public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
this.newStatisticsEntryCallback = fn;
@@ -31,6 +32,23 @@ class Statistics {
this.runStatistics();
}, 1 * 60 * 1000);
}, difference);
this.createCache();
setInterval(this.createCache.bind(this), 600000);
}
public getCache() {
return this.cache;
}
private async createCache() {
this.cache['24h'] = await this.$list24H();
this.cache['1w'] = await this.$list1W();
this.cache['1m'] = await this.$list1M();
this.cache['3m'] = await this.$list3M();
this.cache['6m'] = await this.$list6M();
this.cache['1y'] = await this.$list1Y();
logger.debug('Statistics cache created');
}
private async runStatistics(): Promise<void> {
@@ -64,15 +82,10 @@ class Statistics {
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
const weightVsizeFees: { [feePerWU: number]: number } = {};
const lastItem = logFees.length - 1;
memPoolArray.forEach((transaction) => {
for (let i = 0; i < logFees.length; i++) {
if (
(Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
||
(!Common.isLiquid() && (i === lastItem || transaction.effectiveFeePerVsize < logFees[i + 1]))
) {
if ((logFees[i] === 2000 && transaction.effectiveFeePerVsize >= 2000) || transaction.effectiveFeePerVsize <= logFees[i]) {
if (weightVsizeFees[logFees[i]]) {
weightVsizeFees[logFees[i]] += transaction.vsize;
} else {
@@ -246,58 +259,10 @@ class Statistics {
}
}
private getQueryForDaysAvg(div: number, interval: string) {
return `SELECT
UNIX_TIMESTAMP(added) as added,
CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
CAST(avg(vsize_1) as DOUBLE) as vsize_1,
CAST(avg(vsize_2) as DOUBLE) as vsize_2,
CAST(avg(vsize_3) as DOUBLE) as vsize_3,
CAST(avg(vsize_4) as DOUBLE) as vsize_4,
CAST(avg(vsize_5) as DOUBLE) as vsize_5,
CAST(avg(vsize_6) as DOUBLE) as vsize_6,
CAST(avg(vsize_8) as DOUBLE) as vsize_8,
CAST(avg(vsize_10) as DOUBLE) as vsize_10,
CAST(avg(vsize_12) as DOUBLE) as vsize_12,
CAST(avg(vsize_15) as DOUBLE) as vsize_15,
CAST(avg(vsize_20) as DOUBLE) as vsize_20,
CAST(avg(vsize_30) as DOUBLE) as vsize_30,
CAST(avg(vsize_40) as DOUBLE) as vsize_40,
CAST(avg(vsize_50) as DOUBLE) as vsize_50,
CAST(avg(vsize_60) as DOUBLE) as vsize_60,
CAST(avg(vsize_70) as DOUBLE) as vsize_70,
CAST(avg(vsize_80) as DOUBLE) as vsize_80,
CAST(avg(vsize_90) as DOUBLE) as vsize_90,
CAST(avg(vsize_100) as DOUBLE) as vsize_100,
CAST(avg(vsize_125) as DOUBLE) as vsize_125,
CAST(avg(vsize_150) as DOUBLE) as vsize_150,
CAST(avg(vsize_175) as DOUBLE) as vsize_175,
CAST(avg(vsize_200) as DOUBLE) as vsize_200,
CAST(avg(vsize_250) as DOUBLE) as vsize_250,
CAST(avg(vsize_300) as DOUBLE) as vsize_300,
CAST(avg(vsize_350) as DOUBLE) as vsize_350,
CAST(avg(vsize_400) as DOUBLE) as vsize_400,
CAST(avg(vsize_500) as DOUBLE) as vsize_500,
CAST(avg(vsize_600) as DOUBLE) as vsize_600,
CAST(avg(vsize_700) as DOUBLE) as vsize_700,
CAST(avg(vsize_800) as DOUBLE) as vsize_800,
CAST(avg(vsize_900) as DOUBLE) as vsize_900,
CAST(avg(vsize_1000) as DOUBLE) as vsize_1000,
CAST(avg(vsize_1200) as DOUBLE) as vsize_1200,
CAST(avg(vsize_1400) as DOUBLE) as vsize_1400,
CAST(avg(vsize_1600) as DOUBLE) as vsize_1600,
CAST(avg(vsize_1800) as DOUBLE) as vsize_1800,
CAST(avg(vsize_2000) as DOUBLE) as vsize_2000 \
FROM statistics \
WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
ORDER BY statistics.added DESC;`;
}
private getQueryForDays(div: number, interval: string) {
return `SELECT
UNIX_TIMESTAMP(added) as added,
CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
private getQueryForDays(div: number) {
return `SELECT id, added, unconfirmed_transactions,
tx_per_second,
vbytes_per_second,
vsize_1,
vsize_2,
vsize_3,
@@ -335,17 +300,13 @@ class Statistics {
vsize_1400,
vsize_1600,
vsize_1800,
vsize_2000 \
FROM statistics \
WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
ORDER BY statistics.added DESC;`;
vsize_2000 FROM statistics GROUP BY UNIX_TIMESTAMP(added) DIV ${div} ORDER BY id DESC LIMIT 480`;
}
private async $get(id: number): Promise<OptimizedStatistic | undefined> {
public async $get(id: number): Promise<OptimizedStatistic | undefined> {
try {
const connection = await DB.pool.getConnection();
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
const query = `SELECT * FROM statistics WHERE id = ?`;
const [rows] = await connection.query<any>(query, [id]);
connection.release();
if (rows[0]) {
@@ -359,7 +320,7 @@ class Statistics {
public async $list2H(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 120`;
const query = `SELECT * FROM statistics ORDER BY id DESC LIMIT 120`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -372,7 +333,7 @@ class Statistics {
public async $list24H(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 1440`;
const query = this.getQueryForDays(180);
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -385,7 +346,7 @@ class Statistics {
public async $list1W(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(300, '1 WEEK'); // 5m interval
const query = this.getQueryForDays(1260);
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -398,7 +359,7 @@ class Statistics {
public async $list1M(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(1800, '1 MONTH'); // 30m interval
const query = this.getQueryForDays(5040);
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -411,7 +372,7 @@ class Statistics {
public async $list3M(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(7200, '3 MONTH'); // 2h interval
const query = this.getQueryForDays(15120);
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -424,7 +385,7 @@ class Statistics {
public async $list6M(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(10800, '6 MONTH'); // 3h interval
const query = this.getQueryForDays(30240);
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
@@ -437,46 +398,22 @@ class Statistics {
public async $list1Y(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDays(28800, '1 YEAR'); // 8h interval
const query = this.getQueryForDays(60480);
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
} catch (e) {
logger.err('$list1Y() error' + (e instanceof Error ? e.message : e));
logger.err('$list6M() error' + (e instanceof Error ? e.message : e));
return [];
}
}
public async $list2Y(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDays(28800, "2 YEAR"); // 8h interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
} catch (e) {
logger.err('$list2Y() error' + (e instanceof Error ? e.message : e));
return [];
}
}
public async $list3Y(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDays(43200, "3 YEAR"); // 12h interval
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);
} catch (e) {
logger.err('$list3Y() error' + (e instanceof Error ? e.message : e));
return [];
}
}
private mapStatisticToOptimizedStatistic(statistic: Statistic[]): OptimizedStatistic[] {
return statistic.map((s) => {
return {
id: s.id || 0,
added: s.added,
unconfirmed_transactions: s.unconfirmed_transactions,
tx_per_second: s.tx_per_second,
vbytes_per_second: s.vbytes_per_second,
mempool_byte_weight: s.mempool_byte_weight,
total_fee: s.total_fee,

View File

@@ -2,7 +2,6 @@ import bitcoinApi from './bitcoin/bitcoin-api-factory';
import { TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
import { IEsploraApi } from './bitcoin/esplora-api.interface';
import config from '../config';
import { Common } from './common';
class TransactionUtils {
constructor() { }
@@ -32,8 +31,7 @@ class TransactionUtils {
// @ts-ignore
return transaction;
}
const feePerVbytes = Math.max(Common.isLiquid() ? 0.1 : 1,
(transaction.fee || 0) / (transaction.weight / 4));
const feePerVbytes = Math.max(config.MEMPOOL.NETWORK === 'liquid' ? 0.1 : 1, (transaction.fee || 0) / (transaction.weight / 4));
const transactionExtended: TransactionExtended = Object.assign({
vsize: Math.round(transaction.weight / 4),
feePerVsize: feePerVbytes,

View File

@@ -2,7 +2,7 @@ const configFile = require('../mempool-config.json');
interface IConfig {
MEMPOOL: {
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid';
BACKEND: 'esplora' | 'electrum' | 'none';
HTTP_PORT: number;
SPAWN_CLUSTER_PROCS: number;
@@ -16,7 +16,6 @@ interface IConfig {
MEMPOOL_BLOCKS_AMOUNT: number;
PRICE_FEED_UPDATE_INTERVAL: number;
USE_SECOND_NODE_FOR_MINFEE: boolean;
EXTERNAL_ASSETS: string[];
};
ESPLORA: {
REST_API_URL: string;
@@ -79,7 +78,6 @@ const defaults: IConfig = {
'MEMPOOL_BLOCKS_AMOUNT': 8,
'PRICE_FEED_UPDATE_INTERVAL': 3600,
'USE_SECOND_NODE_FOR_MINFEE': false,
'EXTERNAL_ASSETS': [],
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',

View File

@@ -21,10 +21,6 @@ import backendInfo from './api/backend-info';
import loadingIndicators from './api/loading-indicators';
import mempool from './api/mempool';
import elementsParser from './api/liquid/elements-parser';
import databaseMigration from './api/database-migration';
import syncAssets from './sync-assets';
import icons from './api/liquid/icons';
import { Common } from './api/common';
class Server {
private wss: WebSocket.Server | undefined;
@@ -73,34 +69,23 @@ class Server {
next();
})
.use(express.urlencoded({ extended: true }))
.use(express.text())
;
.use(express.json());
this.server = http.createServer(this.app);
this.wss = new WebSocket.Server({ server: this.server });
this.setUpWebsocketHandling();
await syncAssets.syncAssets();
diskCache.loadMempoolCache();
if (config.DATABASE.ENABLED) {
await checkDbConnection();
try {
await databaseMigration.$initializeOrMigrateDatabase();
} catch (e) {
throw new Error(e instanceof Error ? e.message : 'Error');
}
}
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isMaster) {
statistics.startStatistics();
}
if (Common.isLiquid()) {
icons.loadIcons();
}
fiatConversion.startService();
this.setUpHttpApiRoutes();
@@ -157,7 +142,7 @@ class Server {
if (this.wss) {
websocketHandler.setWebsocketServer(this.wss);
}
if (Common.isLiquid() && config.DATABASE.ENABLED) {
if (config.MEMPOOL.NETWORK === 'liquid') {
blocks.setNewBlockCallback(async () => {
try {
await elementsParser.$parse();
@@ -184,7 +169,6 @@ class Server {
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', routes.getBackendInfo)
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', routes.getInitData)
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', routes.validateAddress)
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', routes.$postTransactionForm)
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
try {
const response = await axios.get('https://mempool.space/api/v1/donations', { responseType: 'stream', timeout: 10000 });
@@ -221,37 +205,17 @@ class Server {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
try {
const response = await axios.get('https://mempool.space/api/v1/translators', { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
try {
const response = await axios.get('https://mempool.space/api/v1/translators/images/' + req.params.id, {
responseType: 'stream', timeout: 10000
});
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
;
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2h', routes.$getStatisticsByTime.bind(routes, '2h'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/24h', routes.$getStatisticsByTime.bind(routes, '24h'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1w', routes.$getStatisticsByTime.bind(routes, '1w'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1m', routes.$getStatisticsByTime.bind(routes, '1m'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3m', routes.$getStatisticsByTime.bind(routes, '3m'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/6m', routes.$getStatisticsByTime.bind(routes, '6m'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2h', routes.get2HStatistics)
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/24h', routes.get24HStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1w', routes.get1WHStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1m', routes.get1MStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3m', routes.get3MStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/6m', routes.get6MStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.get1YStatistics.bind(routes))
;
}
@@ -302,14 +266,7 @@ class Server {
;
}
if (Common.isLiquid()) {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
.get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
;
}
if (Common.isLiquid() && config.DATABASE.ENABLED) {
if (config.MEMPOOL.NETWORK === 'liquid') {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
;

View File

@@ -128,7 +128,10 @@ export interface Statistic {
}
export interface OptimizedStatistic {
id: number;
added: string;
unconfirmed_transactions: number;
tx_per_second: number;
vbytes_per_second: number;
total_fee: number;
mempool_byte_weight: number;

View File

@@ -19,55 +19,37 @@ import loadingIndicators from './api/loading-indicators';
import { Common } from './api/common';
import bitcoinClient from './api/bitcoin/bitcoin-client';
import elementsParser from './api/liquid/elements-parser';
import icons from './api/liquid/icons';
class Routes {
constructor() {}
public async $getStatisticsByTime(time: '2h' | '24h' | '1w' | '1m' | '3m' | '6m' | '1y' | '2y' | '3y', req: Request, res: Response) {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
public async get2HStatistics(req: Request, res: Response) {
const result = await statistics.$list2H();
res.json(result);
}
try {
let result;
switch (time as string) {
case '2h':
result = await statistics.$list2H();
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
break;
case '24h':
result = await statistics.$list24H();
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
break;
case '1w':
result = await statistics.$list1W();
break;
case '1m':
result = await statistics.$list1M();
break;
case '3m':
result = await statistics.$list3M();
break;
case '6m':
result = await statistics.$list6M();
break;
case '1y':
result = await statistics.$list1Y();
break;
case '2y':
result = await statistics.$list2Y();
break;
case '3y':
result = await statistics.$list3Y();
break;
default:
result = await statistics.$list2H();
}
res.json(result);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
public get24HStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['24h']);
}
public get1WHStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['1w']);
}
public get1MStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['1m']);
}
public get3MStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['3m']);
}
public get6MStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['6m']);
}
public get1YStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['1y']);
}
public getInitData(req: Request, res: Response) {
@@ -79,7 +61,7 @@ class Routes {
}
}
public getRecommendedFees(req: Request, res: Response) {
public async getRecommendedFees(req: Request, res: Response) {
if (!mempool.isInSync()) {
res.statusCode = 503;
res.send('Service Unavailable');
@@ -631,7 +613,7 @@ class Routes {
const addressData = await bitcoinApi.$getAddress(req.params.address);
res.json(addressData);
} catch (e) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
if (e instanceof Error && e.message && e.message.indexOf('exceeds') > 0) {
return res.status(413).send(e instanceof Error ? e.message : e);
}
res.status(500).send(e instanceof Error ? e.message : e);
@@ -648,7 +630,7 @@ class Routes {
const transactions = await bitcoinApi.$getAddressTransactions(req.params.address, req.params.txId);
res.json(transactions);
} catch (e) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
if (e instanceof Error && e.message && e.message.indexOf('exceeds') > 0) {
return res.status(413).send(e instanceof Error ? e.message : e);
}
res.status(500).send(e instanceof Error ? e.message : e);
@@ -734,16 +716,14 @@ class Routes {
const nextRetargetHeight = blockHeight + remainingBlocks;
let difficultyChange = 0;
if (remainingBlocks < 1870) {
if (blocksInEpoch > 0) {
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (difficultyChange > 300) {
difficultyChange = 300;
}
if (difficultyChange < -75) {
difficultyChange = -75;
}
if (blocksInEpoch > 0) {
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (difficultyChange > 300) {
difficultyChange = 300;
}
if (difficultyChange < -75) {
difficultyChange = -75;
}
const timeAvgDiff = difficultyChange * 0.1;
@@ -788,55 +768,14 @@ class Routes {
public async $postTransaction(req: Request, res: Response) {
res.setHeader('content-type', 'text/plain');
try {
let rawTx;
if (typeof req.body === 'object') {
rawTx = Object.keys(req.body)[0];
} else {
rawTx = req.body;
}
const txIdResult = await bitcoinApi.$sendRawTransaction(rawTx);
const rawtx = Object.keys(req.body)[0];
const txIdResult = await bitcoinApi.$sendRawTransaction(rawtx);
res.send(txIdResult);
} catch (e: any) {
res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
}
}
public async $postTransactionForm(req: Request, res: Response) {
res.setHeader('content-type', 'text/plain');
const matches = /tx=([a-z0-9]+)/.exec(req.body);
let txHex = '';
if (matches && matches[1]) {
txHex = matches[1];
}
try {
const txIdResult = await bitcoinClient.sendRawTransaction(txHex);
res.send(txIdResult);
} catch (e: any) {
res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
}
}
public getLiquidIcon(req: Request, res: Response) {
const result = icons.getIconByAssetId(req.params.assetId);
if (result) {
res.setHeader('content-type', 'image/png');
res.setHeader('content-length', result.length);
res.send(result);
} else {
res.status(404).send('Asset icon not found');
}
}
public getAllLiquidIcon(req: Request, res: Response) {
const result = icons.getAllIconIds();
if (result) {
res.json(result);
} else {
res.status(404).send('Asset icons not found');
}
}
}
export default new Routes();

View File

@@ -1,32 +0,0 @@
import axios from 'axios';
import * as fs from 'fs';
const fsPromises = fs.promises;
import config from './config';
import logger from './logger';
const PATH = './';
class SyncAssets {
constructor() { }
public async syncAssets() {
for (const url of config.MEMPOOL.EXTERNAL_ASSETS) {
await this.downloadFile(url);
}
}
private async downloadFile(url: string) {
const fileName = url.split('/').slice(-1)[0];
logger.info(`Downloading external asset: ${fileName}...`);
try {
const response = await axios.get(url, {
responseType: 'stream', timeout: 30000
});
await fsPromises.writeFile(PATH + fileName, response.data);
} catch (e: any) {
throw new Error(`Failed to download external asset. ` + e);
}
}
}
export default new SyncAssets();

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"lib": ["es2019", "dom"],
"lib": ["es2019"],
"strict": true,
"noImplicitAny": false,
"sourceMap": false,

101
docker/README.md Normal file
View File

@@ -0,0 +1,101 @@
# Docker
## Initialization
In an empty dir create 2 sub-dirs
```bash
mkdir -p data mysql/data mysql/db-scripts
```
In the `mysql/db-scripts` sub-dir add the `mariadb-structure.sql` file from the mempool repo
Your dir should now look like that:
```bash
$ls -R
.:
data mysql
./data:
./mysql:
data db-scripts
./mysql/data:
./mysql/db-scripts:
mariadb-structure.sql
```
In the main dir add the following `docker-compose.yml`
```bash
version: "3.7"
services:
web:
image: mempool/frontend:latest
user: "1000:1000"
restart: on-failure
stop_grace_period: 1m
command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'"
ports:
- 80:8080
environment:
FRONTEND_HTTP_PORT: "8080"
BACKEND_MAINNET_HTTP_HOST: "api"
api:
image: mempool/backend:latest
user: "1000:1000"
restart: on-failure
stop_grace_period: 1m
command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"
volumes:
- ./data:/backend/cache
environment:
RPC_HOST: "127.0.0.1"
RPC_PORT: "8332"
RPC_USER: "mempool"
RPC_PASS: "mempool"
ELECTRUM_HOST: "127.0.0.1"
ELECTRUM_PORT: "50002"
ELECTRUM_TLS: "false"
MYSQL_HOST: "db"
MYSQL_PORT: "3306"
MYSQL_DATABASE: "mempool"
MYSQL_USER: "mempool"
MYSQL_PASS: "mempool"
BACKEND_MAINNET_HTTP_PORT: "8999"
CACHE_DIR: "/backend/cache"
MEMPOOL_CLEAR_PROTECTION_MINUTES: "20"
db:
image: mariadb:10.5.8
user: "1000:1000"
restart: on-failure
stop_grace_period: 1m
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/db-scripts:/docker-entrypoint-initdb.d
environment:
MYSQL_DATABASE: "mempool"
MYSQL_USER: "mempool"
MYSQL_PASSWORD: "mempool"
MYSQL_ROOT_PASSWORD: "admin"
```
You can update all the environment variables inside the API container, especially the RPC and ELECTRUM ones
## Run it
To run our docker-compose use the following cmd:
```bash
docker-compose up
```
If everything went okay you should see the beautiful mempool :grin:
If you get stuck on "loading blocks", this means the websocket can't connect.
Check your nginx proxy setup, firewalls, etc. and open an issue if you need help.

View File

@@ -1,4 +1,4 @@
FROM node:16.10.0-buster-slim AS builder
FROM node:12-buster-slim AS builder
WORKDIR /build
COPY . .
@@ -8,7 +8,7 @@ RUN apt-get install -y build-essential python3 pkg-config
RUN npm install
RUN npm run build
FROM node:16.10.0-buster-slim
FROM node:12-buster-slim
WORKDIR /backend

View File

@@ -1,62 +1,38 @@
{
"MEMPOOL": {
"NETWORK": "__MEMPOOL_NETWORK__",
"BACKEND": "__MEMPOOL_BACKEND__",
"HTTP_PORT": __MEMPOOL_HTTP_PORT__,
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
"POLL_RATE_MS": __MEMPOOL_POLL_RATE_MS__,
"CACHE_DIR": "__MEMPOOL_CACHE_DIR__",
"CLEAR_PROTECTION_MINUTES": __MEMPOOL_CLEAR_PROTECTION_MINUTES__,
"RECOMMENDED_FEE_PERCENTILE": __MEMPOOL_RECOMMENDED_FEE_PERCENTILE__,
"BLOCK_WEIGHT_UNITS": __MEMPOOL_BLOCK_WEIGHT_UNITS__,
"INITIAL_BLOCKS_AMOUNT": __MEMPOOL_INITIAL_BLOCKS_AMOUNT__,
"MEMPOOL_BLOCKS_AMOUNT": __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__,
"PRICE_FEED_UPDATE_INTERVAL": __MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__,
"USE_SECOND_NODE_FOR_MINFEE": __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__,
"EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__
"NETWORK": "mainnet",
"BACKEND": "electrum",
"HTTP_PORT": __MEMPOOL_BACKEND_MAINNET_HTTP_PORT__,
"SPAWN_CLUSTER_PROCS": 0,
"API_URL_PREFIX": "/api/v1/",
"POLL_RATE_MS": 2000,
"CACHE_DIR": "__MEMPOOL_BACKEND_MAINNET_CACHE_DIR__",
"CLEAR_PROTECTION_MINUTES": __MEMPOOL_BACKEND_CLEAR_PROTECTION_MINUTES__
},
"CORE_RPC": {
"HOST": "__CORE_RPC_HOST__",
"PORT": __CORE_RPC_PORT__,
"USERNAME": "__CORE_RPC_USERNAME__",
"PASSWORD": "__CORE_RPC_PASSWORD__"
"HOST": "__BITCOIN_MAINNET_RPC_HOST__",
"PORT": __BITCOIN_MAINNET_RPC_PORT__,
"USERNAME": "__BITCOIN_MAINNET_RPC_USER__",
"PASSWORD": "__BITCOIN_MAINNET_RPC_PASS__"
},
"ELECTRUM": {
"HOST": "__ELECTRUM_HOST__",
"PORT": __ELECTRUM_PORT__,
"TLS_ENABLED": __ELECTRUM_TLS_ENABLED__
"HOST": "__ELECTRUM_MAINNET_HTTP_HOST__",
"PORT": __ELECTRUM_MAINNET_HTTP_PORT__,
"TLS_ENABLED": __ELECTRUM_MAINNET_TLS_ENABLED__
},
"ESPLORA": {
"REST_API_URL": "__ESPLORA_REST_API_URL__"
},
"SECOND_CORE_RPC": {
"HOST": "__SECOND_CORE_RPC_HOST__",
"PORT": __SECOND_CORE_RPC_PORT__,
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__"
"REST_API_URL": "http://127.0.0.1:3000"
},
"DATABASE": {
"ENABLED": __DATABASE_ENABLED__,
"HOST": "__DATABASE_HOST__",
"PORT": __DATABASE_PORT__,
"DATABASE": "__DATABASE_DATABASE__",
"USERNAME": "__DATABASE_USERNAME__",
"PASSWORD": "__DATABASE_PASSWORD__"
},
"SYSLOG": {
"ENABLED": __SYSLOG_ENABLED__,
"HOST": "__SYSLOG_HOST__",
"PORT": __SYSLOG_PORT__,
"MIN_PRIORITY": "__SYSLOG_MIN_PRIORITY__",
"FACILITY": "__SYSLOG_FACILITY__"
"ENABLED": true,
"HOST": "__MYSQL_HOST__",
"PORT": __MYSQL_PORT__,
"DATABASE": "__MYSQL_DATABASE__",
"USERNAME": "__MYSQL_USERNAME__",
"PASSWORD": "__MYSQL_PASSWORD__"
},
"STATISTICS": {
"ENABLED": __STATISTICS_ENABLED__,
"TX_PER_SECOND_SAMPLE_PERIOD": __STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__
},
"BISQ": {
"ENABLED": __BISQ_ENABLED__,
"DATA_PATH": "__BISQ_DATA_PATH__"
"ENABLED": true,
"TX_PER_SECOND_SAMPLE_PERIOD": 150
}
}

View File

@@ -1,116 +1,41 @@
#!/bin/sh
# MEMPOOL
__MEMPOOL_NETWORK__=${MEMPOOL_NETWORK:=mainnet}
__MEMPOOL_BACKEND__=${MEMPOOL_BACKEND:=electrum}
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
__MEMPOOL_POLL_RATE_MS__=${MEMPOOL_POLL_RATE_MS:=2000}
__MEMPOOL_CACHE_DIR__=${MEMPOOL_CACHE_DIR:=./cache}
__MEMPOOL_CLEAR_PROTECTION_MINUTES__=${MEMPOOL_CLEAR_PROTECTION_MINUTES:=20}
__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__=${MEMPOOL_RECOMMENDED_FEE_PERCENTILE:=50}
__MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=3600}
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
# CORE_RPC
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
__CORE_RPC_PORT__=${CORE_RPC_PORT:=8332}
__CORE_RPC_USERNAME__=${CORE_RPC_USERNAME:=mempool}
__CORE_RPC_PASSWORD__=${CORE_RPC_PASSWORD:=mempool}
#MEMPOOL
__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__=${BACKEND_MAINNET_HTTP_PORT:=8999}
__MEMPOOL_BACKEND_MAINNET_CACHE_DIR__=${CACHE_DIR:=./cache}
__MEMPOOL_BACKEND_CLEAR_PROTECTION_MINUTES__=${MEMPOOL_CLEAR_PROTECTION_MINUTES:=20}
# BITCOIN
__BITCOIN_MAINNET_RPC_HOST__=${RPC_HOST:=127.0.0.1}
__BITCOIN_MAINNET_RPC_PORT__=${RPC_PORT:=8332}
__BITCOIN_MAINNET_RPC_USER__=${RPC_USER:=mempool}
__BITCOIN_MAINNET_RPC_PASS__=${RPC_PASS:=mempool}
# ELECTRUM
__ELECTRUM_HOST__=${ELECTRUM_HOST:=127.0.0.1}
__ELECTRUM_PORT__=${ELECTRUM_PORT:=50002}
__ELECTRUM_TLS_ENABLED__=${ELECTRUM_TLS:=false}
__ELECTRUM_MAINNET_HTTP_HOST__=${ELECTRUM_HOST:=127.0.0.1}
__ELECTRUM_MAINNET_HTTP_PORT__=${ELECTRUM_PORT:=50002} # 50001?
__ELECTRUM_MAINNET_TLS_ENABLED__=${ELECTRUM_TLS:=false}
# MYSQL
__MYSQL_HOST__=${MYSQL_HOST:=127.0.0.1}
__MYSQL_PORT__=${MYSQL_PORT:=3306}
__MYSQL_DATABASE__=${MYSQL_DATABASE:=mempool}
__MYSQL_USERNAME__=${MYSQL_USER:=mempool}
__MYSQL_PASSWORD__=${MYSQL_PASS:=mempool}
# ESPLORA
__ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000}
mkdir -p "${__MEMPOOL_BACKEND_MAINNET_CACHE_DIR__}"
# SECOND_CORE_RPC
__SECOND_CORE_RPC_HOST__=${SECOND_CORE_RPC_HOST:=127.0.0.1}
__SECOND_CORE_RPC_PORT__=${SECOND_CORE_RPC_PORT:=8332}
__SECOND_CORE_RPC_USERNAME__=${SECOND_CORE_RPC_USERNAME:=mempool}
__SECOND_CORE_RPC_PASSWORD__=${SECOND_CORE_RPC_PASSWORD:=mempool}
# DATABASE
__DATABASE_ENABLED__=${DATABASE_ENABLED:=true}
__DATABASE_HOST__=${DATABASE_HOST:=127.0.0.1}
__DATABASE_PORT__=${DATABASE_PORT:=3306}
__DATABASE_DATABASE__=${DATABASE_DATABASE:=mempool}
__DATABASE_USERNAME__=${DATABASE_USERNAME:=mempool}
__DATABASE_PASSWORD__=${DATABASE_PASSWORD:=mempool}
# SYSLOG
__SYSLOG_ENABLED__=${SYSLOG_ENABLED:=false}
__SYSLOG_HOST__=${SYSLOG_HOST:=127.0.0.1}
__SYSLOG_PORT__=${SYSLOG_PORT:=514}
__SYSLOG_MIN_PRIORITY__=${SYSLOG_MIN_PRIORITY:=info}
__SYSLOG_FACILITY__=${SYSLOG_FACILITY:=local7}
# STATISTICS
__STATISTICS_ENABLED__=${STATISTICS_ENABLED:=true}
__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__=${STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD:=150}
# BISQ
__BISQ_ENABLED__=${BISQ_ENABLED:=false}
__BISQ_DATA_PATH__=${BISQ_DATA_PATH:=/bisq/statsnode-data/btc_mainnet/db}
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
sed -i "s/__MEMPOOL_NETWORK__/${__MEMPOOL_NETWORK__}/g" mempool-config.json
sed -i "s/__MEMPOOL_BACKEND__/${__MEMPOOL_BACKEND__}/g" mempool-config.json
sed -i "s/__MEMPOOL_HTTP_PORT__/${__MEMPOOL_HTTP_PORT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_SPAWN_CLUSTER_PROCS__/${__MEMPOOL_SPAWN_CLUSTER_PROCS__}/g" mempool-config.json
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json
sed -i "s/__MEMPOOL_POLL_RATE_MS__/${__MEMPOOL_POLL_RATE_MS__}/g" mempool-config.json
sed -i "s!__MEMPOOL_CACHE_DIR__!${__MEMPOOL_CACHE_DIR__}!g" mempool-config.json
sed -i "s/__MEMPOOL_CLEAR_PROTECTION_MINUTES__/${__MEMPOOL_CLEAR_PROTECTION_MINUTES__}/g" mempool-config.json
sed -i "s/__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__/${__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__}/g" mempool-config.json
sed -i "s/__MEMPOOL_BLOCK_WEIGHT_UNITS__/${__MEMPOOL_BLOCK_WEIGHT_UNITS__}/g" mempool-config.json
sed -i "s/__MEMPOOL_INITIAL_BLOCKS_AMOUNT__/${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__/${__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__}/g" mempool-config.json
sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json
sed -i "s/__MEMPOOL_EXTERNAL_ASSETS__/${__MEMPOOL_EXTERNAL_ASSETS__}/g" mempool-config.json
sed -i "s/__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_USERNAME__/${__CORE_RPC_USERNAME__}/g" mempool-config.json
sed -i "s/__CORE_RPC_PASSWORD__/${__CORE_RPC_PASSWORD__}/g" mempool-config.json
sed -i "s/__ELECTRUM_HOST__/${__ELECTRUM_HOST__}/g" mempool-config.json
sed -i "s/__ELECTRUM_PORT__/${__ELECTRUM_PORT__}/g" mempool-config.json
sed -i "s/__ELECTRUM_TLS_ENABLED__/${__ELECTRUM_TLS_ENABLED__}/g" mempool-config.json
sed -i "s!__ESPLORA_REST_API_URL__!${__ESPLORA_REST_API_URL__}!g" mempool-config.json
sed -i "s/__SECOND_CORE_RPC_HOST__/${__SECOND_CORE_RPC_HOST__}/g" mempool-config.json
sed -i "s/__SECOND_CORE_RPC_PORT__/${__SECOND_CORE_RPC_PORT__}/g" mempool-config.json
sed -i "s/__SECOND_CORE_RPC_USERNAME__/${__SECOND_CORE_RPC_USERNAME__}/g" mempool-config.json
sed -i "s/__SECOND_CORE_RPC_PASSWORD__/${__SECOND_CORE_RPC_PASSWORD__}/g" mempool-config.json
sed -i "s/__DATABASE_ENABLED__/${__DATABASE_ENABLED__}/g" mempool-config.json
sed -i "s/__DATABASE_HOST__/${__DATABASE_HOST__}/g" mempool-config.json
sed -i "s/__DATABASE_PORT__/${__DATABASE_PORT__}/g" mempool-config.json
sed -i "s/__DATABASE_DATABASE__/${__DATABASE_DATABASE__}/g" mempool-config.json
sed -i "s/__DATABASE_USERNAME__/${__DATABASE_USERNAME__}/g" mempool-config.json
sed -i "s/__DATABASE_PASSWORD__/${__DATABASE_PASSWORD__}/g" mempool-config.json
sed -i "s/__SYSLOG_ENABLED__/${__SYSLOG_ENABLED__}/g" mempool-config.json
sed -i "s/__SYSLOG_HOST__/${__SYSLOG_HOST__}/g" mempool-config.json
sed -i "s/__SYSLOG_PORT__/${__SYSLOG_PORT__}/g" mempool-config.json
sed -i "s/__SYSLOG_MIN_PRIORITY__/${__SYSLOG_MIN_PRIORITY__}/g" mempool-config.json
sed -i "s/__SYSLOG_FACILITY__/${__SYSLOG_FACILITY__}/g" mempool-config.json
sed -i "s/__STATISTICS_ENABLED__/${__STATISTICS_ENABLED__}/g" mempool-config.json
sed -i "s/__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__/${__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__}/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/__BITCOIN_MAINNET_RPC_HOST__/${__BITCOIN_MAINNET_RPC_HOST__}/g" mempool-config.json
sed -i "s/__BITCOIN_MAINNET_RPC_PORT__/${__BITCOIN_MAINNET_RPC_PORT__}/g" mempool-config.json
sed -i "s/__BITCOIN_MAINNET_RPC_USER__/${__BITCOIN_MAINNET_RPC_USER__}/g" mempool-config.json
sed -i "s/__BITCOIN_MAINNET_RPC_PASS__/${__BITCOIN_MAINNET_RPC_PASS__}/g" mempool-config.json
sed -i "s/__ELECTRUM_MAINNET_HTTP_HOST__/${__ELECTRUM_MAINNET_HTTP_HOST__}/g" mempool-config.json
sed -i "s/__ELECTRUM_MAINNET_HTTP_PORT__/${__ELECTRUM_MAINNET_HTTP_PORT__}/g" mempool-config.json
sed -i "s/__ELECTRUM_MAINNET_TLS_ENABLED__/${__ELECTRUM_MAINNET_TLS_ENABLED__}/g" mempool-config.json
sed -i "s/__MYSQL_HOST__/${__MYSQL_HOST__}/g" mempool-config.json
sed -i "s/__MYSQL_PORT__/${__MYSQL_PORT__}/g" mempool-config.json
sed -i "s/__MYSQL_DATABASE__/${__MYSQL_DATABASE__}/g" mempool-config.json
sed -i "s/__MYSQL_USERNAME__/${__MYSQL_USERNAME__}/g" mempool-config.json
sed -i "s/__MYSQL_PASSWORD__/${__MYSQL_PASSWORD__}/g" mempool-config.json
sed -i "s!__MEMPOOL_BACKEND_MAINNET_CACHE_DIR__!${__MEMPOOL_BACKEND_MAINNET_CACHE_DIR__}!g" mempool-config.json
sed -i "s/__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__/${__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_BACKEND_CLEAR_PROTECTION_MINUTES__/${__MEMPOOL_BACKEND_CLEAR_PROTECTION_MINUTES__}/g" mempool-config.json
node /backend/dist/index.js

View File

View File

@@ -1,10 +1,23 @@
version: "3.7"
services:
web:
electrum:
build:
context: .
dockerfile: docker/electrum/Dockerfile
user: "1000:1000"
restart: on-failure
command: ""
ports:
- 50001:50001
- 50002:50002
- 4224:4224
- 8332:8332
environment:
FRONTEND_HTTP_PORT: "8080"
BACKEND_MAINNET_HTTP_HOST: "api"
ELECTRUM: "electrum"
# add electrs configs
web:
image: mempool/frontend:latest
user: "1000:1000"
restart: on-failure
@@ -12,19 +25,10 @@ services:
command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'"
ports:
- 80:8080
api:
environment:
MEMPOOL_BACKEND: "none"
CORE_RPC_HOST: "172.27.0.1"
CORE_RPC_PORT: "8332"
CORE_RPC_USERNAME: "mempool"
CORE_RPC_PASSWORD: "mempool"
DATABASE_ENABLED: "true"
DATABASE_HOST: "db"
DATABASE_DATABASE: "mempool"
DATABASE_USERNAME: "mempool"
DATABASE_PASSWORD: "mempool"
STATISTICS_ENABLED: "true"
FRONTEND_HTTP_PORT: "8080"
BACKEND_MAINNET_HTTP_HOST: "api"
api:
image: mempool/backend:latest
user: "1000:1000"
restart: on-failure
@@ -32,15 +36,32 @@ services:
command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"
volumes:
- ./data:/backend/cache
db:
environment:
RPC_HOST: "127.0.0.1"
RPC_PORT: "8332"
RPC_USER: "mempool"
RPC_PASS: "mempool"
ELECTRUM_HOST: "127.0.0.1"
ELECTRUM_PORT: "50002"
ELECTRUM_TLS: "false"
MYSQL_HOST: "db"
MYSQL_PORT: "3306"
MYSQL_DATABASE: "mempool"
MYSQL_USER: "mempool"
MYSQL_PASSWORD: "mempool"
MYSQL_ROOT_PASSWORD: "admin"
MYSQL_PASS: "mempool"
BACKEND_MAINNET_HTTP_PORT: "8999"
CACHE_DIR: "/backend/cache"
MEMPOOL_CLEAR_PROTECTION_MINUTES: "20"
db:
image: mariadb:10.5.8
user: "1000:1000"
restart: on-failure
stop_grace_period: 1m
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/db-scripts:/docker-entrypoint-initdb.d
environment:
MYSQL_DATABASE: "mempool"
MYSQL_USER: "mempool"
MYSQL_PASSWORD: "mempool"
MYSQL_ROOT_PASSWORD: "admin"

View File

@@ -1,4 +1,4 @@
FROM node:16.10.0-buster-slim AS builder
FROM node:12-buster-slim AS builder
ARG commitHash
ENV DOCKER_COMMIT_HASH=${commitHash}

View File

@@ -2,7 +2,7 @@
#backend
gitMaster="\.\.\/\.git\/refs\/heads\/master"
git ls-remote https://github.com/mempool/mempool.git "$1^{}" | awk '{ print $1}' > ./backend/master
git ls-remote https://github.com/mempool/mempool.git $1 | awk '{ print $1}' > ./backend/master
cp ./docker/backend/* ./backend/
sed -i "s/${gitMaster}/master/g" ./backend/src/api/backend-info.ts

3
frontend/.gitignore vendored
View File

@@ -34,7 +34,6 @@ speed-measure-plugin.json
.history/*
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
@@ -50,8 +49,6 @@ Thumbs.db
src/resources/assets.json
src/resources/assets.minimal.json
src/resources/assets-testnet.json
src/resources/assets-testnet.minimal.json
src/resources/pools.json
# environment config

View File

@@ -1,50 +1,11 @@
# mempool-frontend
## Contributing
This package is used for the https://mempool.space, https://liquid.network and https://bisq.markets websites - there are npm scripts to setup all three, which effectively change how BASE_MODULE is configured:
```
$ npm run config:defaults:mempool
$ npm run config:defaults:liquid
$ npm run config:defaults:bisq
```
Changes that affect the frontend codebase only can be done using the production backend so you don't need to spin up the entire Mempool infrastructure. This is very convenient in case you want to quickly improve the UI, fix typos or implement new features that don't require any backend changes.
Make your changes, install the project dependencies and run the frontend server as follows:
```
$ npm install
$ npm run serve:local-prod
```
The frontend will be available at http://localhost:4200/ and all API requests will be proxied to the production server at https://mempool.space
After making your changes, you can run our end-to-end automation suite and check for possible regressions:
Headless:
```
$ npm run config:defaults:mempool && npm run cypress:run
```
Interactive:
```
$ npm run config:defaults:mempool && npm run cypress:open
```
This will open the Cypress test runner, where you can select any of the test files to run.
If all tests are green, submit your PR and it will be reviewed by someone on the team as soon as possible.
## Translations: Transifex Project
## Transifex Project
The mempool frontend strings are localized into 20+ locales:
https://www.transifex.com/mempool/mempool/dashboard/
### Translators
## Translators
* Arabic @baro0k
* Czech @pixelmade2
@@ -66,11 +27,10 @@ https://www.transifex.com/mempool/mempool/dashboard/
* Slovenian @thepkbadger
* Finnish @bio_bitcoin
* Swedish @softsimon_
* Thai @Gusb3ll
* Turkish @stackmore
* Ukrainian @volbil
* Vietnamese @bitcoin_vietnam
* Chinese @wdljt
* Russian @TonyCrusoe @Bitconan
* Romanian @mirceavesa
* Macedonian @SkechBoy
* Macedonian @SkechBoy

View File

@@ -94,10 +94,6 @@
"translation": "src/locale/messages.sv.xlf",
"baseHref": "/sv/"
},
"th": {
"translation": "src/locale/messages.th.xlf",
"baseHref": "/th/"
},
"tr": {
"translation": "src/locale/messages.tr.xlf",
"baseHref": "/tr/"
@@ -215,11 +211,11 @@
"browserTarget": "mempool:build:production"
},
"local": {
"proxyConfig": "proxy.conf.local.js",
"proxyConfig": "proxy.conf.json",
"verbose": true
},
"staging": {
"proxyConfig": "proxy.conf.js",
"proxyConfig": "proxy.stg.conf.json",
"disableHostCheck": true,
"host": "0.0.0.0",
"verbose": true
@@ -255,6 +251,20 @@
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"tsconfig.server.json",
"cypress/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {

View File

@@ -11,6 +11,5 @@
"retries": {
"runMode": 3,
"openMode": 0
},
"chromeWebSecurity": false
}
}
}

View File

@@ -1,52 +0,0 @@
{
"rbfTransaction": {
"txid": "8913ec7ba0ede285dbd120e46f6d61a28f2903c10814a6f6c4f97d0edf3e1f46",
"version": 2,
"locktime": 632699,
"vin": [
{
"txid": "02238126a63ea2669c5f378012180ef8b54402a949316f9b2f1352c51730a086",
"vout": 0,
"prevout": {
"scriptpubkey": "a914f8e495456956c833e5e8c69b9a9dc041aa14c72f87",
"scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 f8e495456956c833e5e8c69b9a9dc041aa14c72f OP_EQUAL",
"scriptpubkey_type": "p2sh",
"scriptpubkey_address": "3QP3LMD8veT5GtWV83Nosif2Bhr73857VB",
"value": 25000000
},
"scriptsig": "22002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
"scriptsig_asm": "OP_PUSHBYTES_34 002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
"witness": [
"",
"3044022009e2d3a8e645f65bc89c8492cd9c08e6fb02609fd402214884a754a1970145340220575bb325429def59f3a3f1e22d9740a3feecbe97438ff3bb5796b2c46b3c477f01",
"3044022039c34372882da8fc1c1243bd72b5e7e5e6870301ef56bdebb87bc647fb50f9b5022071a704ee77d742f78b10e45be675d4c45a5f31e884139e75c975144fde70e41701",
"522102346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf70421021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b52ae"
],
"is_coinbase": false,
"sequence": 4294967293,
"inner_redeemscript_asm": "OP_0 OP_PUSHBYTES_32 43288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
"inner_witnessscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 02346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf704 OP_PUSHBYTES_33 021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b OP_PUSHNUM_2 OP_CHECKMULTISIG"
}
],
"vout": [
{
"scriptpubkey": "a914fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f87",
"scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f OP_EQUAL",
"scriptpubkey_type": "p2sh",
"scriptpubkey_address": "3QnNmDhZS7toHA7bhhbTPBdtpLJoeecq5c",
"value": 13986350
},
{
"scriptpubkey": "76a914edc93d0446deec1c2d514f3a490f050096e74e0e88ac",
"scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 edc93d0446deec1c2d514f3a490f050096e74e0e OP_EQUALVERIFY OP_CHECKSIG",
"scriptpubkey_type": "p2pkh",
"scriptpubkey_address": "1NgJDkTUqJxxCAAZrrsC87kWag5kphrRtM",
"value": 11000000
}
],
"size": 372,
"weight": 828,
"fee": 1.5,
"status": { "confirmed": false }
}
}

View File

@@ -1,8 +1,8 @@
describe('Bisq', () => {
const baseModule = Cypress.env("BASE_MODULE");
const basePath = '';
let baseModule;
beforeEach(() => {
baseModule = (Cypress.env('BASE_MODULE') && Cypress.env('BASE_MODULE') === 'bisq') ? '' : '/bisq';
cy.intercept('/sockjs-node/info*').as('socket');
cy.intercept('/bisq/api/markets/hloc?market=btc_usd&interval=day').as('hloc');
cy.intercept('/bisq/api/markets/ticker').as('ticker');
@@ -23,15 +23,15 @@ describe('Bisq', () => {
});
});
if (baseModule === 'bisq') {
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") !== 'liquid') {
it('loads the dashboard', () => {
cy.visit(`${basePath}`);
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
});
it('loads the transactions screen', () => {
cy.visit(`${basePath}`);
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.get('.table > tr').should('have.length', 50);
@@ -39,7 +39,7 @@ describe('Bisq', () => {
});
it('loads the blocks screen', () => {
cy.visit(`${basePath}`);
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait('@blocks');
@@ -48,7 +48,7 @@ describe('Bisq', () => {
});
it('loads the stats screen', () => {
cy.visit(`${basePath}`);
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait('@stats');
@@ -56,17 +56,18 @@ describe('Bisq', () => {
});
it('loads the api screen', () => {
cy.visit(`${basePath}`);
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.section-header').should('have.length.at.least', 1);
cy.get('.endpoint-container').should('have.length.at.least', 1);
cy.get('.card').should('have.length.at.least', 1);
cy.get('.card').first().click();
cy.get('.card-body');
});
});
it('shows blocks pagination with 5 pages (desktop)', () => {
cy.viewport(760, 800);
cy.visit(`${basePath}/blocks`);
cy.visit(`${baseModule}/blocks`);
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 5 pages + 4 buttons = 9 buttons
@@ -75,13 +76,13 @@ describe('Bisq', () => {
it('shows blocks pagination with 3 pages (mobile)', () => {
cy.viewport(669, 800);
cy.visit(`${basePath}/blocks`);
cy.visit(`${baseModule}/blocks`);
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 3 pages + 4 buttons = 7 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -1,8 +1,8 @@
describe('Liquid', () => {
const baseModule = Cypress.env("BASE_MODULE");
const basePath = '';
let baseModule;
beforeEach(() => {
baseModule = (Cypress.env('BASE_MODULE') && Cypress.env('BASE_MODULE') === 'liquid') ? '' : '/liquid';
cy.intercept('/liquid/api/block/**').as('block');
cy.intercept('/liquid/api/blocks/').as('blocks');
cy.intercept('/liquid/api/tx/**/outspends').as('outspends');
@@ -16,36 +16,30 @@ describe('Liquid', () => {
});
});
if (baseModule === 'liquid') {
it('check first mempool block after skeleton loads', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('#mempool-block-0 > .blockLink').should('exist');
});
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") !== 'bisq') {
it('loads the dashboard', () => {
cy.visit(`${basePath}`);
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
});
it('loads the blocks page', () => {
cy.visit(`${basePath}/blocks`);
cy.visit(`${baseModule}/blocks`);
cy.waitForSkeletonGone();
});
it('loads a specific block page', () => {
cy.visit(`${basePath}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`);
cy.visit(`${baseModule}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`);
cy.waitForSkeletonGone();
});
it('loads the graphs page', () => {
cy.visit(`${basePath}/graphs`);
cy.visit(`${baseModule}/graphs`);
cy.waitForSkeletonGone();
});
it('loads the tv page - desktop', () => {
cy.visit(`${basePath}`);
cy.visit(`${baseModule}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
@@ -53,7 +47,7 @@ describe('Liquid', () => {
});
it('loads the graphs page - mobile', () => {
cy.visit(`${basePath}`)
cy.visit(`${baseModule}`)
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.viewport('iphone-6');
@@ -62,63 +56,15 @@ describe('Liquid', () => {
});
});
it('renders unconfidential addresses correctly on mobile', () => {
cy.viewport('iphone-6');
cy.visit(`${basePath}/address/ex1qqmmjdwrlg59c8q4l75sj6wedjx57tj5grt8pat`);
cy.waitForSkeletonGone();
//TODO: Add proper IDs for these selectors
const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody';
const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)';
cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => {
cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => {
expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth));
});
});
});
describe('peg in/peg out', () => {
it('loads peg in addresses', () => {
cy.visit(`${basePath}/tx/fe764f7bedfc2a37b29d9c8aef67d64a57d253a6b11c5a55555cfd5826483a58`);
cy.waitForSkeletonGone();
//TODO: Change to an element id so we don't assert on a string
cy.get('#table-tx-vin').should('contain', 'Peg-in');
cy.get('#table-tx-vin a').click().then(() => {
cy.waitForSkeletonGone();
if (baseModule === 'liquid') {
cy.url().should('eq', 'https://mempool.space/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592');
} else {
//TODO: Use an environment variable to get the hostname
cy.url().should('eq', 'http://localhost:4200/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592');
}
});
});
it('loads peg out addresses', () => {
cy.visit(`${basePath}/tx/ecf6eba04ffb3946faa172343c87162df76f1a57b07b0d6dc6ad956b13376dc8`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout a').first().click().then(() => {
cy.waitForSkeletonGone();
if (baseModule === 'liquid') {
cy.url().should('eq', 'https://mempool.space/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR');
} else {
//TODO: Use an environment variable to get the hostname
cy.url().should('eq', 'http://localhost:4200/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR');
}
//TODO: Add a custom class so we don't assert on a string
cy.get('.badge').should('contain','Liquid Peg Out');
});
});
});
describe('assets', () => {
it('shows the assets screen', () => {
cy.visit(`${basePath}/assets`);
cy.visit(`${baseModule}/assets`);
cy.waitForSkeletonGone();
cy.get('table tr').should('have.length.at.least', 5);
});
it('allows searching assets', () => {
cy.visit(`${basePath}/assets`);
cy.visit(`${baseModule}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
cy.get('table tr').should('have.length', 1);
@@ -126,7 +72,7 @@ describe('Liquid', () => {
});
it('shows a specific asset ID', () => {
cy.visit(`${basePath}/assets`);
cy.visit(`${baseModule}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid AUD').then(() => {
cy.get('table tr td:nth-of-type(1) a').click();
@@ -138,27 +84,27 @@ describe('Liquid', () => {
describe('unblinded TX', () => {
it('should not show an unblinding error message for regular txs', () => {
cy.visit(`${basePath}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`);
cy.visit(`${baseModule}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`);
cy.waitForSkeletonGone();
cy.get('.error-unblinded' ).should('not.exist');
});
it('show unblinded TX', () => {
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', 'assetBox');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show empty unblinded TX', () => {
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=`);
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
});
it('show invalid unblinded TX hex', () => {
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123`);
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
@@ -166,36 +112,36 @@ describe('Liquid', () => {
});
it('show first unblinded vout', () => {
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc`);
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox');
});
it('show second unblinded vout', () => {
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`);
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show invalid error unblinded TX', () => {
cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c`);
cy.visit(`${baseModule}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.');
});
it('shows asset peg in/out and burn transactions', () => {
cy.visit(`${basePath}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
cy.visit(`${baseModule}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').not('.assetBox');
cy.get('#table-tx-vin tr').not('.assetBox');
});
it('prevents regressing issue #644', () => {
cy.visit(`${basePath}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`);
cy.visit(`${baseModule}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`);
cy.waitForSkeletonGone();
});
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -1,37 +1,5 @@
import { emitMempoolInfo, dropWebSocket } from "../../support/websocket";
const baseModule = Cypress.env("BASE_MODULE");
//Credit: https://github.com/bahmutov/cypress-examples/blob/6cedb17f83a3bb03ded13cf1d6a3f0656ca2cdf5/docs/recipes/overlapping-elements.md
/**
* Returns true if two DOM rectangles are overlapping
* @param {DOMRect} rect1 the bounding client rectangle of the first element
* @param {DOMRect} rect2 the bounding client rectangle of the second element
* @returns {boolean}
*/
const areOverlapping = (rect1, rect2) => {
// if one rectangle is on the left side of the other
if (rect1.right < rect2.left || rect2.right < rect1.left) {
return false
}
// if one rectangle is above the other
if (rect1.bottom < rect2.top || rect2.bottom < rect1.top) {
return false
}
// the rectangles must overlap
return true
}
/**
* Returns the bounding rectangle of the first DOM
* element in the given jQuery object.
*/
const getRectangle = ($el) => $el[0].getBoundingClientRect();
describe('Mainnet', () => {
beforeEach(() => {
//cy.intercept('/sockjs-node/info*').as('socket');
@@ -52,13 +20,7 @@ describe('Mainnet', () => {
});
});
if (baseModule === 'mempool') {
it('check first mempool block after skeleton loads', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('#mempool-block-0 > .blockLink').should('exist');
});
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
it('loads the status screen', () => {
cy.visit('/status');
@@ -86,7 +48,7 @@ describe('Mainnet', () => {
cy.get('.badge', {timeout: 25000}).should('not.exist');
emitMempoolInfo({
'params': {
command: 'init'
loaded: true
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
@@ -99,22 +61,6 @@ describe('Mainnet', () => {
cy.waitForSkeletonGone();
});
it('check op_return tx tooltip', () => {
cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2');
cy.waitForSkeletonGone();
cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('onmouseover');
cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('mouseenter');
cy.get('.tooltip-inner').should('be.visible');
});
it('check op_return coinbase tooltip', () => {
cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2');
cy.waitForSkeletonGone();
cy.get('div > a > .badge').first().trigger('onmouseover');
cy.get('div > a > .badge').first().trigger('mouseenter');
cy.get('.tooltip-inner').should('be.visible');
});
describe('search', () => {
it('allows searching for partial Bitcoin addresses', () => {
cy.visit('/');
@@ -141,33 +87,18 @@ describe('Mainnet', () => {
});
['BC1PQYQSZQ', 'bc1PqYqSzQ'].forEach((searchTerm) => {
it(`allows searching for partial case insensitive bech32m addresses: ${searchTerm}`, () => {
it(`allows searching for partial case insensitive bc1 addresses: ${searchTerm}`, () => {
cy.visit('/');
cy.get('.search-box-container > .form-control').type(searchTerm).then(() => {
cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1);
cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => {
cy.url().should('include', '/address/bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e');
cy.url().should('include', '/address/bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3wf0qm');
cy.waitForSkeletonGone();
cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address');
});
});
});
});
['BC1Q000375VXCU', 'bC1q000375vXcU'].forEach((searchTerm) => {
it(`allows searching for partial case insensitive bech32 addresses: ${searchTerm}`, () => {
cy.visit('/');
cy.get('.search-box-container > .form-control').type(searchTerm).then(() => {
cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1);
cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => {
cy.url().should('include', '/address/bc1q000375vxcuf5v04lmwy22vy2thvhqkxghgq7dy');
cy.waitForSkeletonGone();
cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address');
});
});
});
});
});
describe('blocks navigation', () => {
@@ -296,10 +227,12 @@ describe('Mainnet', () => {
cy.changeNetwork("testnet");
cy.changeNetwork("signet");
cy.changeNetwork("liquid");
cy.changeNetwork("mainnet");
cy.changeNetwork("bisq");
});
it.skip('loads the dashboard with the skeleton blocks', () => {
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
@@ -311,7 +244,7 @@ describe('Mainnet', () => {
emitMempoolInfo({
'params': {
command: 'init'
loaded: true
}
});
@@ -336,33 +269,6 @@ describe('Mainnet', () => {
});
});
describe('graphs page', () => {
it('check buttons - mobile', () => {
cy.viewport('iphone-6');
cy.visit('/graphs');
cy.waitForSkeletonGone();
cy.get('.small-buttons > :nth-child(2)').should('be.visible');
cy.get('#dropdownFees').should('be.visible');
cy.get('.btn-group').should('be.visible');
});
it('check buttons - tablet', () => {
cy.viewport('ipad-2');
cy.visit('/graphs');
cy.waitForSkeletonGone();
cy.get('.small-buttons > :nth-child(2)').should('be.visible');
cy.get('#dropdownFees').should('be.visible');
cy.get('.btn-group').should('be.visible');
});
it('check buttons - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/graphs');
cy.waitForSkeletonGone();
cy.get('.small-buttons > :nth-child(2)').should('be.visible');
cy.get('#dropdownFees').should('be.visible');
cy.get('.btn-group').should('be.visible');
});
});
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/');
@@ -375,7 +281,7 @@ describe('Mainnet', () => {
});
});
it('loads the tv screen - mobile', () => {
it.only('loads the tv screen - mobile', () => {
cy.viewport('iphone-6');
cy.visit('/tv');
cy.waitForSkeletonGone();
@@ -456,79 +362,7 @@ describe('Mainnet', () => {
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7);
});
});
describe('RBF transactions', () => {
it('shows RBF transactions properly (mobile)', () => {
cy.viewport('iphone-xr');
cy.mockMempoolSocket();
cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
cy.waitForSkeletonGone();
emitMempoolInfo({
'params': {
command: 'init'
}
});
cy.get('#mempool-block-0');
emitMempoolInfo({
'params': {
command: 'rbfTransaction'
}
});
cy.get('.alert-mempool').should('be.visible');
cy.get('.alert-mempool').invoke('css', 'width').then((alertWidth) => {
cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth);
});
cy.get('.btn-success').then(getRectangle).then((rectA) => {
cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
});
});
});
it('shows RBF transactions properly (desktop)', () => {
cy.viewport('macbook-16');
cy.mockMempoolSocket();
cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
cy.waitForSkeletonGone();
emitMempoolInfo({
'params': {
command: 'init'
}
});
cy.get('#mempool-block-0');
emitMempoolInfo({
'params': {
command: 'rbfTransaction'
}
});
cy.get('.alert-mempool').should('be.visible');
const alertLocator = '.alert-mempool';
const tableLocator = '.container-xl > :nth-child(3)';
cy.get(tableLocator).invoke('css', 'width').then((firstWidth) => {
cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth);
});
cy.get('.btn-success').then(getRectangle).then((rectA) => {
cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
});
});
});
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -1,7 +1,5 @@
import { emitMempoolInfo } from "../../support/websocket";
const baseModule = Cypress.env("BASE_MODULE");
describe('Signet', () => {
beforeEach(() => {
cy.intercept('/api/block-height/*').as('block-height');
@@ -11,19 +9,13 @@ describe('Signet', () => {
});
if (baseModule === 'mempool') {
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
it('loads the dashboard', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
});
it('check first mempool block after skeleton loads', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('#mempool-block-0 > .blockLink').should('exist');
});
it.skip('loads the dashboard with the skeleton blocks', () => {
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/signet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
@@ -134,6 +126,6 @@ describe('Signet', () => {
});
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -1,7 +1,5 @@
import { confirmAddress, emitMempoolInfo, sendWsMock, showNewTx, startTrackingAddress } from "../../support/websocket";
const baseModule = Cypress.env("BASE_MODULE");
describe('Testnet', () => {
beforeEach(() => {
cy.intercept('/api/block-height/*').as('block-height');
@@ -10,20 +8,14 @@ describe('Testnet', () => {
cy.intercept('/api/tx/*/outspends').as('tx-outspends');
});
if (baseModule === 'mempool') {
if (Cypress.env("BASE_MODULE") === '' || Cypress.env("BASE_MODULE") === 'mempool') {
it('loads the dashboard', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
});
it('check first mempool block after skeleton loads', () => {
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('#mempool-block-0 > .blockLink').should('exist');
});
it.skip('loads the dashboard with the skeleton blocks', () => {
it('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/testnet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
@@ -131,6 +123,6 @@ describe('Testnet', () => {
});
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
it.skip("Tests cannot be run on the selected BASE_MODULE");
}
});

View File

@@ -1,13 +1 @@
const fs = require('fs');
const CONFIG_FILE = 'mempool-frontend-config.json';
module.exports = (on, config) => {
if (fs.existsSync(CONFIG_FILE)) {
let contents = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
config.env.BASE_MODULE = contents.BASE_MODULE ? contents.BASE_MODULE : 'mempool';
} else {
config.env.BASE_MODULE = 'mempool';
}
return config;
}
module.exports = (on, config) => {}

View File

@@ -31,19 +31,19 @@ export const mockWebSocket = () => {
cy.on('window:before:load', (win) => {
const winWebSocket = win.WebSocket;
cy.stub(win, 'WebSocket').callsFake((url) => {
console.log(url);
console.log(url);
if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0)) {
const { server, websocket } = createMock(url);
win.mockServer = server;
win.mockServer.on('connection', (socket) => {
win.mockSocket = socket;
win.mockSocket.send('{"action":"init"}');
win.mockSocket.send('{"action":"init"}');
});
win.mockServer.on('message', (message) => {
console.log(message);
});
win.mockServer.on('message', (message) => {
console.log(message);
});
return websocket;
} else {
@@ -68,13 +68,7 @@ export const emitMempoolInfo = ({
//TODO: Use network specific mocks
case "signet":
case "testnet":
case "mainnet":
default:
break;
}
switch (params.command) {
case "init": {
win.mockSocket.send('{"action":"init"}');
win.mockSocket.send('{"action":"want","data":["blocks","stats","mempool-blocks","live-2h-chart"]}');
win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}');
@@ -84,16 +78,6 @@ export const emitMempoolInfo = ({
cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'ascii').then((fixture) => {
win.mockSocket.send(JSON.stringify(fixture));
});
break;
}
case "rbfTransaction": {
cy.readFile('cypress/fixtures/mainnet_rbf.json', 'ascii').then((fixture) => {
win.mockSocket.send(JSON.stringify(fixture));
});
break;
}
default:
break;
}
});
cy.waitForSkeletonGone();
@@ -105,4 +89,4 @@ export const dropWebSocket = (() => {
win.mockServer.simulate("error");
});
return cy.wait(500);
});
});

View File

@@ -2,7 +2,6 @@
"TESTNET_ENABLED": false,
"SIGNET_ENABLED": false,
"LIQUID_ENABLED": false,
"LIQUID_TESTNET_ENABLED": false,
"BISQ_ENABLED": false,
"BISQ_SEPARATE_BACKEND": false,
"ITEMS_PER_PAGE": 10,
@@ -10,10 +9,7 @@
"NGINX_PROTOCOL": "http",
"NGINX_HOSTNAME": "127.0.0.1",
"NGINX_PORT": "80",
"BLOCK_WEIGHT_UNITS": 4000000,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"BASE_MODULE": "mempool",
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
"LIQUID_WEBSITE_URL": "https://liquid.network",
"BISQ_WEBSITE_URL": "https://bisq.markets"
"BLOCK_WEIGHT_UNITS": 4000000,
"BASE_MODULE": "mempool"
}

18336
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,10 +34,7 @@
"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",
"generate-config": "node generate-config.js",
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
"build-mempool-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
"build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
"build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
"build-mempool.js": "tsc | browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
"test": "ng test",
"lint": "ng lint",
"e2e": "npm run generate-config && ng e2e",
@@ -56,27 +53,25 @@
"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"
},
"dependencies": {
"@angular-devkit/build-angular": "^13.1.2",
"@angular/animations": "~13.1.1",
"@angular/cli": "~13.0.4",
"@angular/common": "~13.1.1",
"@angular/compiler": "~13.1.1",
"@angular/core": "~13.1.1",
"@angular/forms": "~13.1.1",
"@angular/localize": "^13.1.1",
"@angular/platform-browser": "~13.1.1",
"@angular/platform-browser-dynamic": "~13.1.1",
"@angular/platform-server": "~13.1.1",
"@angular/router": "~13.1.1",
"@angular/animations": "~12.2.6",
"@angular/common": "~12.2.6",
"@angular/compiler": "~12.2.6",
"@angular/core": "~12.2.6",
"@angular/forms": "~12.2.6",
"@angular/localize": "^12.2.6",
"@angular/platform-browser": "~12.2.6",
"@angular/platform-browser-dynamic": "~12.2.6",
"@angular/platform-server": "~12.2.6",
"@angular/router": "~12.2.6",
"@fortawesome/angular-fontawesome": "^0.8.2",
"@fortawesome/fontawesome-common-types": "^0.2.35",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@juggle/resize-observer": "^3.3.1",
"@mempool/mempool.js": "2.3.0",
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
"@mempool/mempool.js": "^2.2.4",
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
"@nguniversal/express-engine": "11.2.1",
"@types/qrcode": "1.4.1",
"@types/qrcode": "^1.3.4",
"bootstrap": "4.5.0",
"browserify": "^17.0.0",
"clipboard": "^2.0.4",
@@ -87,7 +82,7 @@
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-echarts": "^7.0.1",
"ngx-infinite-scroll": "^10.0.1",
"qrcode": "1.5.0",
"qrcode": "^1.4.4",
"rxjs": "^6.6.7",
"tinyify": "^3.0.0",
"tlite": "^0.1.9",
@@ -95,8 +90,10 @@
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular/compiler-cli": "~13.1.1",
"@angular/language-service": "~13.1.1",
"@angular-devkit/build-angular": "^12.2.6",
"@angular/cli": "~12.2.6",
"@angular/compiler-cli": "~12.2.6",
"@angular/language-service": "~12.2.6",
"@nguniversal/builders": "^11.2.1",
"@types/express": "^4.17.0",
"@types/jasmine": "~3.6.0",
@@ -113,14 +110,14 @@
"karma-jasmine-html-reporter": "^1.5.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.4.4"
"typescript": "~4.3.5"
},
"optionalDependencies": {
"@cypress/schematic": "^1.3.0",
"cypress": "^9.1.1",
"cypress-fail-on-console-error": "^2.1.3",
"cypress": "^8.3.1",
"cypress-fail-on-console-error": "^2.1.0",
"cypress-wait-until": "^1.7.1",
"mock-socket": "^9.0.3",
"start-server-and-test": "^1.12.6"
}
}
}

View File

@@ -15,6 +15,7 @@ try {
throw new Error(e);
} else {
console.log(`${CONFIG_FILE_NAME} file not found, using default config`);
}
}
@@ -24,7 +25,6 @@ PROXY_CONFIG = [
'/api/**', '!/api/v1/ws',
'!/bisq', '!/bisq/**', '!/bisq/',
'!/liquid', '!/liquid/**', '!/liquid/',
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
'/testnet/api/**', '/signet/api/**'
],
target: "https://mempool.space",
@@ -58,33 +58,7 @@ PROXY_CONFIG = [
ws: true,
secure: false,
changeOrigin: true
},
{
context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'],
target: "https://liquid.network/testnet",
pathRewrite: {
"^/api/liquidtestnet/": "/liquidtestnet/api"
},
ws: true,
secure: false,
changeOrigin: true
}
];
if (configContent && configContent.BASE_MODULE == "liquid") {
PROXY_CONFIG.push({
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'],
target: "https://liquid.network",
secure: false,
changeOrigin: true,
});
} else {
PROXY_CONFIG.push({
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'],
target: "https://mempool.space",
secure: false,
changeOrigin: true,
});
}
module.exports = PROXY_CONFIG;

114
frontend/proxy.conf.json Normal file
View File

@@ -0,0 +1,114 @@
{
"/api/v1": {
"target": "http://localhost:8999/",
"secure": false
},
"/api/v1/ws": {
"target": "http://localhost:8999/",
"secure": false,
"ws": true
},
"/api/": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/api/": "/api/v1/"
}
},
"/testnet/api/v1": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/testnet/api/v1": "/api/v1"
}
},
"/testnet/api/v1/ws": {
"target": "http://localhost:8999/",
"secure": false,
"ws": true,
"pathRewrite": {
"^/testnet/api": "/api/v1/ws"
}
},
"/testnet/api/": {
"target": "http://localhost:50001/",
"secure": false,
"pathRewrite": {
"^/testnet/api": ""
}
},
"/signet/api/v1": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/signet/api/v1": "/api/v1"
}
},
"/signet/api/v1/ws": {
"target": "http://localhost:8999/",
"secure": false,
"ws": true,
"pathRewrite": {
"^/signet/api": "/api/v1/ws"
}
},
"/signet/api/": {
"target": "http://localhost:50001/",
"secure": false,
"pathRewrite": {
"^/signet/api": ""
}
},
"/liquid/api/v1/ws": {
"target": "http://localhost:8999/",
"secure": false,
"ws": true,
"pathRewrite": {
"^/liquid/api": "/api/v1/ws"
}
},
"/liquid/api/v1/": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/liquid/api/": "/api/"
}
},
"/liquid/api/": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/liquid/api/": "/api/v1/"
}
},
"/bisq/api/": {
"target": "http://localhost:8999/",
"secure": false,
"pathRewrite": {
"^/bisq/api/": "/api/v1/bisq/"
}
},
"/bisq/api/v1/ws": {
"target": "http://localhost:8999/",
"secure": false,
"ws": true,
"pathRewrite": {
"^/bisq/api": "/api/v1/ws"
}
},
"/resources/assets.minimal.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
},
"/resources/assets.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
},
"/resources/pools.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
}
}

View File

@@ -1,73 +0,0 @@
const fs = require('fs');
let PROXY_CONFIG = require('./proxy.conf.js');
const BACKEND_CONFIG_FILE_NAME = '../backend/mempool-config.json';
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
let backendConfigContent;
let frontendConfigContent;
// Read frontend config
try {
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
frontendConfigContent = 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`);
}
}
// Read backend config
try {
const rawConfig = fs.readFileSync(BACKEND_CONFIG_FILE_NAME);
backendConfigContent = JSON.parse(rawConfig);
console.log(`${BACKEND_CONFIG_FILE_NAME} file found, using provided config`);
} catch (e) {
console.log(e);
if (e.code !== 'ENOENT') {
throw new Error(e);
} else {
console.log(`${BACKEND_CONFIG_FILE_NAME} file not found, using default config`);
}
}
// 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);
module.exports = PROXY_CONFIG;

View File

@@ -0,0 +1,99 @@
{
"/api/v1/ws": {
"target": "https://mempool.space",
"secure": false,
"ws": true
},
"/api": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true,
"logLevel": "debug",
"timeout": 3600000
},
"/testnet/api/v1/ws": {
"target": "https://mempool.space/testnet",
"secure": false,
"ws": true,
"loglevel": "debug",
"pathRewrite": {
"^/testnet/api": "/api/v1/ws"
}
},
"/testnet/api": {
"target": "https://mempool.space",
"secure": true,
"changeOrigin": true,
"loglevel": "debug",
"pathRewrite": {
"/testnet/api": "/testnet/api"
},
"timeout": 3600000
},
"/signet/api/v1/ws": {
"target": "https://mempool.space/signet",
"secure": false,
"ws": true,
"loglevel": "debug",
"pathRewrite": {
"^/signet/api": "/api/v1/ws"
}
},
"/signet/api": {
"target": "https://mempool.space",
"secure": true,
"changeOrigin": true,
"loglevel": "debug",
"pathRewrite": {
"/signet/api": "/signet/api"
},
"timeout": 3600000
},
"/bisq/api/v1/ws": {
"target": "https://mempool.space/bisq",
"secure": false,
"ws": true,
"pathRewrite": {
"^/bisq/api": "/api/v1/ws"
}
},
"/bisq/api": {
"target": "https://mempool.space/bisq",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/bisq/api/": "/api/v1/bisq/"
},
"timeout": 3600000
},
"/liquid/api/v1/ws": {
"target": "https://mempool.space",
"secure": false,
"ws": true
},
"/liquid/api": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/liquid/api/": "/liquid/api/"
},
"timeout": 3600000
},
"/resources/assets.minimal.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
},
"/resources/assets.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
},
"/resources/pools.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
}
}

View File

@@ -0,0 +1,100 @@
{
"/api/v1/ws": {
"target": "https://mempool.ninja",
"secure": false,
"ws": true
},
"/api/*": {
"target": "https://mempool.ninja",
"secure": false,
"changeOrigin": true,
"logLevel": "debug",
"pathRewrite": {
"^/api": "https://mempool.ninja/api"
},
"timeout": 3600000
},
"/testnet/api/v1/ws": {
"target": "https://mempool.ninja/testnet",
"secure": false,
"ws": true,
"pathRewrite": {
"^/testnet/api": "/api/v1/ws"
}
},
"/testnet/api/v1/*": {
"target": "https://mempool.ninja/testnet",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/testnet/api/v1": "/api/v1"
},
"timeout": 3600000
},
"/signet/api/v1/ws": {
"target": "https://mempool.ninja/signet",
"secure": false,
"ws": true,
"pathRewrite": {
"^/signet/api": "/api/v1/ws"
}
},
"/signet/api/v1/*": {
"target": "https://mempool.ninja/signet",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/signet/api/v1": "/api/v1"
},
"timeout": 3600000
},
"/bisq/api/v1/ws": {
"target": "https://mempool.ninja/bisq",
"secure": false,
"ws": true,
"pathRewrite": {
"^/bisq/api": "/api/v1/ws"
}
},
"/bisq/api/*": {
"target": "https://mempool.ninja/bisq",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/bisq/api/": "/api/v1/bisq/"
},
"timeout": 3600000
},
"/liquid/api/v1/ws": {
"target": "https://mempool.ninja/liquid",
"secure": false,
"ws": true,
"pathRewrite": {
"^/liquid/api": "/api/v1/ws"
}
},
"/liquid/api/*": {
"target": "https://mempool.ninja/liquid",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/liquid/api/": "/api/liquid/"
},
"timeout": 3600000
},
"/resources/assets.minimal.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
},
"/resources/assets.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
},
"/resources/pools.json": {
"target": "https://mempool.space",
"secure": false,
"changeOrigin": true
}
}

View File

@@ -14,24 +14,19 @@ import { AssetsComponent } from './assets/assets.component';
import { StatusViewComponent } from './components/status-view/status-view.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
import { DocsComponent } from './components/docs/docs.component';
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component';
import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component';
import { SponsorComponent } from './components/sponsor/sponsor.component';
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
let routes: Routes = [
{
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -66,21 +61,9 @@ let routes: Routes = [
path: 'about',
component: AboutComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
component: ApiDocsComponent,
},
{
path: 'terms-of-service',
@@ -106,16 +89,81 @@ let routes: Routes = [
],
},
{
path: 'testnet',
path: 'liquid',
children: [
{
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
path: '',
component: StartComponent,
children: [
{
path: '',
component: DashboardComponent
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'address/:id',
component: AddressComponent
},
{
path: 'asset/:id',
component: AssetComponent
},
{
path: 'assets',
component: AssetsComponent,
},
{
path: 'api',
component: ApiDocsComponent,
},
],
},
{
path: 'tv',
component: TelevisionComponent
},
{
path: 'status',
component: StatusViewComponent
},
{
path: '**',
redirectTo: ''
},
]
},
{
path: 'testnet',
children: [
{
path: '',
component: MasterPageComponent,
children: [
{
path: '',
component: StartComponent,
@@ -151,21 +199,9 @@ let routes: Routes = [
children: [],
component: AddressComponent
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
component: ApiDocsComponent,
},
],
},
@@ -190,10 +226,6 @@ let routes: Routes = [
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -229,21 +261,9 @@ let routes: Routes = [
children: [],
component: AddressComponent
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
component: ApiDocsComponent,
},
],
},
@@ -261,6 +281,11 @@ let routes: Routes = [
},
]
},
{
path: 'bisq',
component: MasterPageComponent,
loadChildren: () => import('./bisq/bisq.module').then(m => m.BisqModule)
},
{
path: 'tv',
component: TelevisionComponent,
@@ -300,10 +325,6 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
path: '',
component: DashboardComponent
},
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: 'tx/:id',
component: TransactionComponent
@@ -338,109 +359,9 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
path: 'assets',
component: AssetsComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
},
{
path: 'about',
component: AboutComponent,
},
{
path: 'terms-of-service',
component: TermsOfServiceComponent
},
{
path: 'privacy-policy',
component: PrivacyPolicyComponent
},
{
path: 'trademark-policy',
component: TrademarkPolicyComponent
},
{
path: 'sponsor',
component: SponsorComponent,
},
],
},
{
path: 'testnet',
component: LiquidMasterPageComponent,
children: [
{
path: '',
component: StartComponent,
children: [
{
path: '',
component: DashboardComponent
},
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: 'tx/:id',
component: TransactionComponent
},
{
path: 'block/:id',
component: BlockComponent
},
{
path: 'mempool-block/:id',
component: MempoolBlockComponent
},
],
},
{
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'graphs',
component: StatisticsComponent,
},
{
path: 'address/:id',
component: AddressComponent
},
{
path: 'asset/:id',
component: AssetComponent
},
{
path: 'assets',
component: AssetsComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
component: ApiDocsComponent,
},
{
path: 'about',
@@ -487,4 +408,3 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@@ -123,20 +123,9 @@ export const languages: Language[] = [
// { code: 'sh', name: 'Srpskohrvatski / српскохрватски' },// Serbo-Croatian
{ code: 'fi', name: 'Suomi' }, // Finnish
{ code: 'sv', name: 'Svenska' }, // Swedish
{ code: 'th', name: 'ไทย' }, // Thai
// { code: 'th', name: 'ไทย' }, // Thai
{ code: 'tr', name: 'Türkçe' }, // Turkish
{ code: 'uk', name: 'Українська' }, // Ukrainian
{ code: 'vi', name: 'Tiếng Việt' }, // Vietnamese
{ code: 'zh', name: '中文' }, // Chinese
];
export const specialBlocks = {
'709632': {
labelEvent: 'Taproot 🌱 activation',
labelEventCompleted: 'Taproot 🌱 has been activated!',
},
'840000': {
labelEvent: 'Halving 🥳',
labelEventCompleted: 'Block Subsidy has halved to 3.125 BTC per block',
}
};

View File

@@ -48,20 +48,15 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
import { ApiDocsComponent } from './components/docs/api-docs.component';
import { DocsComponent } from './components/docs/docs.component';
import { ApiDocsNavComponent } from './components/docs/api-docs-nav.component';
import { CodeTemplateComponent } from './components/docs/code-template.component';
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
import { CodeTemplateComponent } from './components/api-docs/code-template.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component';
import { StorageService } from './services/storage.service';
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
import { LanguageService } from './services/language.service';
import { SponsorComponent } from './components/sponsor/sponsor.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [
@@ -103,9 +98,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
PrivacyPolicyComponent,
TrademarkPolicyComponent,
SponsorComponent,
PushTransactionComponent,
DocsComponent,
ApiDocsNavComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
@@ -115,7 +107,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
BrowserAnimationsModule,
InfiniteScrollModule,
NgbTypeaheadModule,
NgbModule,
FontAwesomeModule,
SharedModule,
NgxEchartsModule.forRoot({
@@ -129,7 +120,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
AudioService,
SeoService,
StorageService,
LanguageService,
{ provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true }
],
bootstrap: [AppComponent]
@@ -166,7 +156,5 @@ export class AppModule {
library.addIcons(faCaretDown);
library.addIcons(faAngleRight);
library.addIcons(faAngleLeft);
library.addIcons(faBook);
library.addIcons(faListUl);
}
}

View File

@@ -1,11 +1,11 @@
<div class="container-xl">
<div class="title-asset">
<h1 i18n="Registered assets page header">Registered assets</h1>
</div>
<h1 style="float: left;" i18n="Registered assets page header">Registered assets</h1>
<br>
<div class="clearfix"></div>
<form [formGroup]="searchForm" class="form-inline">
<div class="input-group mb-2">
<div class="input-group m-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>
@@ -27,7 +27,7 @@
<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>
</tr>
</tbody>
</table>
@@ -52,7 +52,7 @@
<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>
</tr>
</tbody>
</table>
@@ -68,4 +68,4 @@
</div>
<br>
<br>

View File

@@ -3,11 +3,4 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.title-asset {
h1 {
line-height: 1;
margin: 0px;
padding-bottom: 10px;
}
}
}

View File

@@ -7,7 +7,6 @@ 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',
@@ -16,8 +15,7 @@ import { StateService } from '../services/state.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetsComponent implements OnInit {
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
nativeAssetId = environment.nativeAssetId;
assets: AssetExtended[];
assetsCache: AssetExtended[];
searchForm: FormGroup;
@@ -36,7 +34,6 @@ export class AssetsComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private seoService: SeoService,
private stateService: StateService,
) { }
ngOnInit() {
@@ -55,22 +52,12 @@ export class AssetsComponent implements OnInit {
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,
});
}
// @ts-ignore
this.assets.push({
name: 'Liquid Bitcoin',
ticker: 'L-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();

View File

@@ -1,11 +1,9 @@
<div class="container-xl">
<div class="title-block">
<h1><ng-template [ngIf]="blockHeight" i18n="shared.block-title">Block <ng-container *ngTemplateOutlet="blockTemplateContent"></ng-container></ng-template></h1>
<h1><ng-template [ngIf]="blockHeight" i18n="block.block">Block <a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
</div>
<ng-template #blockTemplateContent><a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template>
<div class="clearfix"></div>
<ng-template [ngIf]="!isLoading && !error">

View File

@@ -105,12 +105,10 @@
</ng-container>
</div>
<app-language-selector></app-language-selector>
<app-language-selector *ngIf="stateService.env.BASE_MODULE !== 'bisq'"></app-language-selector>
<div class="text-small text-center mt-3">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
</div>
</div>

View File

@@ -33,7 +33,7 @@ export class BisqMainDashboardComponent implements OnInit {
) { }
ngOnInit(): void {
this.seoService.resetTitle();
this.seoService.setTitle(`Markets`);
this.websocketService.want(['blocks']);
this.usdPrice$ = this.stateService.conversions$.asObservable().pipe(

View File

@@ -1,19 +1,20 @@
<div class="container-xl">
<div class="title-block">
<ng-template [ngIf]="!isLoading && !error">
<div class="title-block">
<div>
<div class="title">
<h1 i18n="shared.transaction">Transaction</h1>
</div>
<span class="tx-link float-left">
<div class="tx-link">
<a [routerLink]="['/bisq-tx' | relativeUrl, bisqTx.id]">
<span class="d-inline d-lg-none">{{ bisqTx.id | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ bisqTx.id }}</span>
</a>
<app-clipboard [text]="bisqTx.id"></app-clipboard>
</span>
<span class="grow"></span>
</div>
<div class="container-buttons">
<button *ngIf="(latestBlock$ | async) as latestBlock" type="button" class="btn btn-sm btn-success float-right">
<ng-container *ngTemplateOutlet="latestBlock.height - bisqTx.blockHeight + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - bisqTx.blockHeight + 1}"></ng-container>
@@ -21,8 +22,6 @@
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
</button>
</div>
</div>
<div class="clearfix"></div>
<div class="box transaction-container">
@@ -85,33 +84,25 @@
<br>
<div class="title">
<h2 i18n="transaction.details">Details</h2>
</div>
<h2 i18n="transaction.details">Details</h2>
<app-bisq-transaction-details [tx]="bisqTx"></app-bisq-transaction-details>
<br>
<div class="title">
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
</div>
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
<app-bisq-transfers [tx]="bisqTx"></app-bisq-transfers>
<br>
</ng-template>
</div>
</ng-template>
<ng-template [ngIf]="isLoading && !error">
<ng-template [ngIf="isLoading && !error">
<div class="clearfix"></div>
<div class="title-block">
<div class="title">
<h1 i18n="shared.transaction">Transaction</h1>
</div>
</div>
<div class="box">
<div class="row">
<div class="col-sm">
@@ -121,14 +112,6 @@
<td class="td-width"><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td class="td-width"><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td class="td-width"><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
@@ -139,10 +122,6 @@
<td class="td-width"><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td class="td-width"><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
@@ -151,10 +130,7 @@
<br>
<div class="title">
<h2 i18n="transaction.details">Details</h2>
</div>
<h2 i18n="transaction.details">Details</h2>
<div class="box">
<table class="table table-borderless table-striped">
<tbody>
@@ -175,30 +151,18 @@
<br>
<div class="title">
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
</div>
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
<div class="box">
<div class="row">
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
</div>
@@ -214,4 +178,4 @@
</div>
</ng-template>
</div>
</div>

View File

@@ -7,12 +7,11 @@ import { BisqBlockComponent } from './bisq-block/bisq-block.component';
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
import { DocsComponent } from '../components/docs/docs.component';
import { ApiDocsComponent } from '../components/api-docs/api-docs.component';
import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.component';
import { BisqMarketComponent } from './bisq-market/bisq-market.component';
import { BisqMainDashboardComponent } from './bisq-main-dashboard/bisq-main-dashboard.component';
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
const routes: Routes = [
{
@@ -31,10 +30,6 @@ const routes: Routes = [
path: 'market/:pair',
component: BisqMarketComponent,
},
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: 'tx/:id',
component: BisqTransactionComponent
@@ -60,21 +55,9 @@ const routes: Routes = [
path: 'about',
component: AboutComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
redirectTo: 'docs/api/rest'
component: ApiDocsComponent,
},
{
path: 'terms-of-service',

View File

@@ -16,7 +16,6 @@ export function calcSegwitFeeGains(tx: Transaction) {
const isP2sh = vin.prevout.scriptpubkey_type === 'p2sh';
const isP2wsh = vin.prevout.scriptpubkey_type === 'v0_p2wsh';
const isP2wpkh = vin.prevout.scriptpubkey_type === 'v0_p2wpkh';
const isP2tr = vin.prevout.scriptpubkey_type === 'v1_p2tr';
const op = vin.scriptsig ? vin.scriptsig_asm.split(' ')[0] : null;
const isP2sh2Wpkh = isP2sh && !!vin.witness && op === 'OP_PUSHBYTES_22';
@@ -26,7 +25,6 @@ export function calcSegwitFeeGains(tx: Transaction) {
// Native Segwit - P2WPKH/P2WSH (Bech32)
case isP2wpkh:
case isP2wsh:
case isP2tr:
// maximal gains: the scriptSig is moved entirely to the witness part
realizedGains += witnessSize(vin) * 3;
// XXX P2WSH output creation is more expensive, should we take this into consideration?

View File

@@ -4,7 +4,7 @@
<span style="margin-left: auto; margin-right: -20px; margin-bottom: -20px">&trade;</span>
<img class="logo" src="./resources/mempool-logo-bigger.png" />
<div class="version">
v{{ packetJsonVersion }} [<a href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]
v{{ packetJsonVersion }} [{{ frontendGitCommitHash }}]
</div>
</div>
@@ -23,20 +23,20 @@
<a target="_blank" href="https://twitter.com/mempool">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
</a>
<a target="_blank" href="https://t.me/mempoolspace">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M446.7 98.6l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1L117.8 284 16.2 252.2c-22.1-6.9-22.5-22.1 4.6-32.7L418.2 66.4c18.4-6.9 34.5 4.1 28.5 32.2z"></path></svg>
</a>
<a target="_blank" href="https://keybase.io/team/mempool">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M286.17 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18zm111.92-147.6c-9.5-14.62-39.37-52.45-87.26-73.71q-9.1-4.06-18.38-7.27a78.43 78.43 0 0 0-47.88-104.13c-12.41-4.1-23.33-6-32.41-5.77-.6-2-1.89-11 9.4-35L198.66 32l-5.48 7.56c-8.69 12.06-16.92 23.55-24.34 34.89a51 51 0 0 0-8.29-1.25c-41.53-2.45-39-2.33-41.06-2.33-50.61 0-50.75 52.12-50.75 45.88l-2.36 36.68c-1.61 27 19.75 50.21 47.63 51.85l8.93.54a214 214 0 0 0-46.29 35.54C14 304.66 14 374 14 429.77v33.64l23.32-29.8a148.6 148.6 0 0 0 14.56 37.56c5.78 10.13 14.87 9.45 19.64 7.33 4.21-1.87 10-6.92 3.75-20.11a178.29 178.29 0 0 1-15.76-53.13l46.82-59.83-24.66 74.11c58.23-42.4 157.38-61.76 236.25-38.59 34.2 10.05 67.45.69 84.74-23.84.72-1 1.2-2.16 1.85-3.22a156.09 156.09 0 0 1 2.8 28.43c0 23.3-3.69 52.93-14.88 81.64-2.52 6.46 1.76 14.5 8.6 15.74 7.42 1.57 15.33-3.1 18.37-11.15C429 443 434 414 434 382.32c0-38.58-13-77.46-35.91-110.92zM142.37 128.58l-15.7-.93-1.39 21.79 13.13.78a93 93 0 0 0 .32 19.57l-22.38-1.34a12.28 12.28 0 0 1-11.76-12.79L107 119c1-12.17 13.87-11.27 13.26-11.32l29.11 1.73a144.35 144.35 0 0 0-7 19.17zm148.42 172.18a10.51 10.51 0 0 1-14.35-1.39l-9.68-11.49-34.42 27a8.09 8.09 0 0 1-11.13-1.08l-15.78-18.64a7.38 7.38 0 0 1 1.34-10.34l34.57-27.18-14.14-16.74-17.09 13.45a7.75 7.75 0 0 1-10.59-1s-3.72-4.42-3.8-4.53a7.38 7.38 0 0 1 1.37-10.34L214 225.19s-18.51-22-18.6-22.14a9.56 9.56 0 0 1 1.74-13.42 10.38 10.38 0 0 1 14.3 1.37l81.09 96.32a9.58 9.58 0 0 1-1.74 13.44zM187.44 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18z"></path></svg>
</a>
<a target="_blank" href="https://matrix.to/#/#mempool:bitcoin.kyoto">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="matrix" class="svg-inline--fa fa-matrix fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 1792"><path fill="currentColor" d="M40.467 163.152v1465.696H145.92V1664H0V128h145.92v35.152zm450.757 464.64v74.14h2.069c19.79-28.356 43.717-50.215 71.483-65.575 27.765-15.656 59.963-23.336 96-23.336 34.56 0 66.165 6.795 94.818 20.086 28.652 13.293 50.216 37.22 65.28 70.893 16.246-23.926 38.4-45.194 66.166-63.507 27.766-18.314 60.848-27.472 98.954-27.472 28.948 0 55.828 3.545 80.64 10.635 24.812 7.088 45.785 18.314 63.508 33.968 17.722 15.656 31.31 35.742 41.354 60.85 9.747 25.107 14.768 55.236 14.768 90.683v366.573h-150.35V865.28c0-18.314-.59-35.741-2.068-51.987-1.476-16.247-5.316-30.426-11.52-42.24-6.499-12.112-15.656-21.563-28.062-28.653-12.405-7.088-29.242-10.634-50.214-10.634-21.268 0-38.4 4.135-51.397 12.112-12.997 8.27-23.336 18.608-30.72 31.901-7.386 12.997-12.407 27.765-14.77 44.602-2.363 16.542-3.84 33.379-3.84 50.216v305.133H692.971v-307.2c0-16.247-.294-32.197-1.18-48.149-.591-15.95-3.84-30.424-9.157-44.011-5.317-13.293-14.178-24.223-26.585-32.197-12.406-7.976-30.425-12.112-54.646-12.112-7.088 0-16.542 1.478-28.062 4.726-11.52 3.25-23.04 9.157-33.968 18.02-10.93 8.86-20.383 21.563-28.063 38.103-7.68 16.543-11.52 38.4-11.52 65.28v317.834H349.44V627.792zm1004.309 1001.056V163.152H1390.08V128H1536v1536h-145.92v-35.152z"/></svg>
</a>
</div>
<div class="enterprise-sponsor">
<h3 i18n="about.sponsors.enterprise.withRocket">Enterprise Sponsors 🚀</h3>
<div class="wrapper">
<a href="https://spiral.xyz/" target="_blank" title="Spiral">
<img class="image" src="/resources/profile/spiral.svg" />
<span>Spiral</span>
<a href="https://squarecrypto.org/" target="_blank" title="Square Crypto">
<img class="image" src="/resources/profile/sqcrypto.svg" />
<span>Square</span>
</a>
<a href="https://gemini.com/" target="_blank" title="Gemini">
<img class="image" src="/resources/profile/gemini.svg" />
@@ -54,10 +54,6 @@
<img class="image" src="/resources/profile/unchained.svg" />
<span>Unchained</span>
</a>
<a href="https://blockstream.com/" target="_blank" title="Blockstream">
<img class="image" src="/resources/profile/blockstream.svg" />
<span>Blockstream</span>
</a>
</div>
</div>
@@ -102,10 +98,6 @@
<img class="image" src="/resources/profile/ronindojo.png" />
<span>RoninDojo</span>
</a>
<a href="https://github.com/runcitadel/dashboard" target="_blank" title="Citadel">
<img class="image" src="/resources/profile/runcitadel.svg" />
<span>Citadel</span>
</a>
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
<img class="image" src="/resources/profile/electrum.jpg" />
<span>Electrum</span>
@@ -146,6 +138,10 @@
<img class="image" src="/resources/profile/satpile.jpg" />
<span>Satpile</span>
</a>
<a href="https://github.com/btcontract/lnwallet" target="_blank" title="Bitcoin Lightning Wallet">
<img class="image" src="/resources/profile/blw.png" />
<span>BLW</span>
</a>
</div>
</div>
@@ -164,45 +160,19 @@
</div>
</div>
<ng-container *ngIf="translators$ | async | keyvalue as translators else loadingSponsors">
<div class="community-sponsor">
<h3 i18n="about.translators">Project Translators</h3>
<div class="wrapper">
<ng-template ngFor let-translator [ngForOf]="translators">
<a [href]="'https://twitter.com/' + translator.value" target="_blank" [title]="translator.key">
<img class="image" [src]="'/api/v1/translators/images/' + translator.value" />
</a>
</ng-template>
</div>
</div>
<br>
</ng-container>
<ng-container *ngIf="allContributors$ | async as contributors else loadingSponsors">
<div class="contributors">
<h3 i18n="about.contributors">Project Contributors</h3>
<div class="wrapper">
<ng-template ngFor let-contributor [ngForOf]="contributors.regular">
<div class="contributors">
<h3 i18n="about.contributors">Project Contributors</h3>
<div class="wrapper">
<ng-container *ngIf="contributors$ | async as contributors; else loadingSponsors">
<ng-template ngFor let-contributor [ngForOf]="contributors">
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" />
<span>{{ contributor.name }}</span>
</a>
</ng-template>
</div>
</ng-container>
</div>
<div class="maintainers" *ngIf="contributors.core.length">
<h3 i18n="about.project_members">Project Members</h3>
<div class="wrapper">
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">
<img class="image" [src]="'/api/v1/contributors/images/' + contributor.id" />
<span>{{ contributor.name }}</span>
</a>
</ng-template>
</div>
</div>
</ng-container>
</div>
<div class="maintainers">
<h3 i18n="about.maintainers">Project Maintainers</h3>
@@ -254,7 +224,7 @@
</div>
<div class="footer-version" *ngIf="officialMempoolSpace">
{{ (backendInfo$ | async)?.hostname }} (v{{ (backendInfo$ | async )?.version }}) [<a href="https://github.com/mempool/mempool/commit/{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}">{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}</a>]
{{ (backendInfo$ | async)?.hostname }} (v{{ (backendInfo$ | async )?.version }}) [{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}]
</div>
</div>

View File

@@ -116,7 +116,7 @@
&:hover {
text-decoration: none;
img {
transform: scale(1.1);
box-shadow: 0px 0px 20px #1bd8f4;
}
}
img, span{
@@ -180,4 +180,4 @@
.no-about-margin {
height: 10px;
}
}

View File

@@ -6,8 +6,6 @@ import { Observable } from 'rxjs';
import { ApiService } from 'src/app/services/api.service';
import { IBackendInfo } from 'src/app/interfaces/websocket.interface';
import { Router } from '@angular/router';
import { map } from 'rxjs/operators';
import { ITranslators } from 'src/app/interfaces/node-api.interface';
@Component({
selector: 'app-about',
@@ -18,8 +16,7 @@ import { ITranslators } from 'src/app/interfaces/node-api.interface';
export class AboutComponent implements OnInit {
backendInfo$: Observable<IBackendInfo>;
sponsors$: Observable<any>;
translators$: Observable<ITranslators>;
allContributors$: Observable<any>;
contributors$: Observable<any>;
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH;
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
@@ -40,25 +37,7 @@ export class AboutComponent implements OnInit {
this.websocketService.want(['blocks']);
this.sponsors$ = this.apiService.getDonation$();
this.translators$ = this.apiService.getTranslators$()
.pipe(
map((translators) => {
for (const t in translators) {
if (translators[t] === '') {
delete translators[t]
}
}
return translators;
})
);
this.allContributors$ = this.apiService.getContributor$().pipe(
map((contributors) => {
return {
regular: contributors.filter((user) => !user.core_constributor),
core: contributors.filter((user) => user.core_constributor),
};
})
);
this.contributors$ = this.apiService.getContributor$();
}
sponsor() {

View File

@@ -1,14 +1,13 @@
<div class="container-xl">
<div class="title-address">
<h1 i18n="shared.address">Address</h1>
<div class="tx-link">
<a [routerLink]="['/address/' | relativeUrl, addressString]" >
<span class="d-inline d-lg-none">{{ addressString | shortenString : 18 }}</span>
<span class="d-none d-lg-inline">{{ addressString }}</span>
</a>
<app-clipboard [text]="addressString"></app-clipboard>
</div>
<h1 i18n="shared.address">Address</h1>
<div class="tx-link">
<a [routerLink]="['/address/' | relativeUrl, addressString]" >
<span class="d-inline d-lg-none">{{ addressString | shortenString : 18 }}</span>
<span class="d-none d-lg-inline">{{ addressString }}</span>
</a>
<app-clipboard [text]="addressString"></app-clipboard>
</div>
<br>
<div class="clearfix"></div>
@@ -21,15 +20,12 @@
<tbody>
<tr *ngIf="addressInfo && addressInfo.unconfidential">
<td i18n="address.unconfidential">Unconfidential</td>
<td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">
<span class="d-inline d-lg-none">{{ addressInfo.unconfidential | shortenString : 14 }}</span>
<span class="d-none d-lg-inline">{{ addressInfo.unconfidential }}</span>
</a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
<td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">{{ addressInfo.unconfidential }}</a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
</tr>
<ng-template [ngIf]="!address.electrum">
<tr>
<td i18n="address.total-received">Total received</td>
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="received" [noFiat]="true"></app-amount></td>
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="receieved" [noFiat]="true"></app-amount></td>
</tr>
<tr>
<td i18n="address.total-sent">Total sent</td>
@@ -38,7 +34,7 @@
</ng-template>
<tr>
<td i18n="address.balance">Balance</td>
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="received - sent" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="received - sent"></app-fiat></span></td>
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="receieved - sent" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="receieved - sent"></app-fiat></span></td>
</tr>
</tbody>
</table>
@@ -54,13 +50,12 @@
</div>
<br>
<div class="title-tx">
<h2>
<ng-template [ngIf]="!transactions?.length">&nbsp;</ng-template>
<ng-template i18n="X of X Address Transaction" [ngIf]="transactions?.length === 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction</ng-template>
<ng-template i18n="X of X Address Transactions (Plural)" [ngIf]="transactions?.length > 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions</ng-template>
</h2>
</div>
<h2>
<ng-template [ngIf]="!transactions?.length">&nbsp;</ng-template>
<ng-template i18n="X of X Address Transaction" [ngIf]="transactions?.length === 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction</ng-template>
<ng-template i18n="X of X Address Transactions (Plural)" [ngIf]="transactions?.length > 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions</ng-template>
</h2>
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
@@ -90,7 +85,7 @@
</ng-template>
<ng-template [ngIf]="retryLoadMore">
<ng-template [ngIf]="retryLoadmore">
<br>
<button type="button" class="btn btn-outline-info btn-sm" (click)="loadMore()"><fa-icon [icon]="['fas', 'redo-alt']" [fixedWidth]="true"></fa-icon></button>
</ng-template>
@@ -119,7 +114,7 @@
</div>
<div class="w-100 d-block d-md-none"></div>
<div class="col">
</div>
</div>
</div>
@@ -127,11 +122,10 @@
</ng-template>
<ng-template [ngIf]="error">
<br>
<div class="text-center">
<span i18n="address.error.loading-address-data">Error loading address data.</span>
<br>
<ng-template #displayServerError><i class="small">({{ error.error }})</i></ng-template>
<ng-template #displayServerError><i>{{ error.error }}</i></ng-template>
<ng-template [ngIf]="error.status === 413 || error.status === 405" [ngIfElse]="displayServerError">
<ng-container i18n="Electrum server limit exceeded error">
<i>The number of transactions on this address exceeds the Electrum server limit</i>
@@ -142,8 +136,6 @@
<a href="https://mempool.space/address/{{ addressString }}" target="_blank">https://mempool.space/address/{{ addressString }}</a>
<br>
<a href="http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}" target="_blank">http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}</a>
<br><br>
<i class="small">({{ error.error }})</i>
</ng-template>
</div>
</ng-template>

View File

@@ -35,22 +35,17 @@
h1 {
margin: 0px;
padding: 0px;
margin-right: 10px;
font-size: 1.9rem;
@media (min-width: 576px) {
font-size: 2rem;
float: left;
}
@media (min-width: 768px) {
font-size: 2.5rem;
margin-right: 10px;
}
}
.address-link {
line-height: 56px;
line-height: 56px;
margin-left: 0px;
top: -2px;
position: relative;
position: relative;
@media (min-width: 768px) {
line-height: 69px;
}
@@ -74,20 +69,10 @@ h1 {
.tx-link {
display: block;
height: 100%;
top: 9px;
width: 100%;
top: 14px;
position: relative;
@media (min-width: 576px) {
top: 11px;
}
@media (min-width: 768px) {
top: 17px;
top: 20px;
}
}
.title-tx {
h2 {
line-height: 1;
margin-bottom: 10px;
}
}
}

View File

@@ -24,7 +24,7 @@ export class AddressComponent implements OnInit, OnDestroy {
isLoadingAddress = true;
transactions: Transaction[];
isLoadingTransactions = true;
retryLoadMore = false;
retryLoadmore = false;
error: any;
mainSubscription: Subscription;
addressLoadingStatus$: Observable<number>;
@@ -33,7 +33,7 @@ export class AddressComponent implements OnInit, OnDestroy {
totalConfirmedTxCount = 0;
loadedConfirmedTxCount = 0;
txCount = 0;
received = 0;
receieved = 0;
sent = 0;
private tempTransactions: Transaction[];
@@ -99,7 +99,7 @@ export class AddressComponent implements OnInit, OnDestroy {
.pipe(
filter((address) => !!address),
tap((address: Address) => {
if ((this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') && /^([m-zA-HJ-NP-Z1-9]{26,35}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[a-km-zA-HJ-NP-Z1-9]{80})$/.test(address.address)) {
if (this.stateService.network === 'liquid' && /^([m-zA-HJ-NP-Z1-9]{26,35}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[a-km-zA-HJ-NP-Z1-9]{80})$/.test(address.address)) {
this.apiService.validateAddress$(address.address)
.subscribe((addressInfo) => {
this.addressInfo = addressInfo;
@@ -183,7 +183,7 @@ export class AddressComponent implements OnInit, OnDestroy {
});
transaction.vout.forEach((vout) => {
if (vout.scriptpubkey_address === this.address.address) {
this.received += vout.value;
this.receieved += vout.value;
}
});
});
@@ -206,7 +206,7 @@ export class AddressComponent implements OnInit, OnDestroy {
return;
}
this.isLoadingTransactions = true;
this.retryLoadMore = false;
this.retryLoadmore = false;
this.electrsApiService.getAddressTransactionsFromHash$(this.address.address, this.lastTransactionTxId)
.subscribe((transactions: Transaction[]) => {
this.lastTransactionTxId = transactions[transactions.length - 1].txid;
@@ -216,12 +216,12 @@ export class AddressComponent implements OnInit, OnDestroy {
},
(error) => {
this.isLoadingTransactions = false;
this.retryLoadMore = true;
this.retryLoadmore = true;
});
}
updateChainStats() {
this.received = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum;
this.receieved = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum;
this.sent = this.address.chain_stats.spent_txo_sum + this.address.mempool_stats.spent_txo_sum;
this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count;
this.totalConfirmedTxCount = this.address.chain_stats.tx_count;

View File

@@ -2,13 +2,12 @@
<span class="fiat">{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
</ng-container>
<ng-template #viewFiatVin>
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
<ng-template [ngIf]="network === 'liquid' && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
<span i18n="shared.confidential">Confidential</span>
</ng-template>
<ng-template #default>
&lrm;{{ satoshis / 100000000 | number : digitsInfo }}
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
<ng-template [ngIf]="network === 'signet'">s</ng-template>BTC</span>
</ng-template>

View File

@@ -0,0 +1,899 @@
<ng-container *ngIf="{ val: network$ | async } as network">
<div class="container-xl text-left">
<div class="text-center">
<h2>{{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">API Service</ng-container></h2>
</div>
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
<li *ngIf="network.val !== 'bisq' && network.val !== 'liquid'" [ngbNavItem]="0">
<a ngbNavLink i18n="api-docs.tab.general|API Docs tab for General">General</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark">
<ngb-panel id="difficultyAdjustment" *ngIf="network.val !== 'liquid'">
<ng-template ngbPanelTitle>
<span>GET Difficulty Adjustment</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="difficulty">
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.difficulty)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/difficulty-adjustment</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about difficulty adjustment.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.difficulty" [network]="network.val" ></app-code-template>
</div>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li *ngIf="network.val === 'bisq'" [ngbNavItem]="0">
<a ngbNavLink i18n="api-docs.tab.bsq|API Docs tab for BSQ">Markets</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
<ngb-panel id="bisqMarketsCurrencies">
<ng-template ngbPanelTitle>
<span>GET Market Currencies</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketsCurrencies)" target="_blank">GET {{ baseNetworkUrl }}/api/currencies</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of available currencies for a given base currency. </div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketsCurrencies" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="marketDepth">
<ng-template ngbPanelTitle>
<span>GET Market Depth</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketDepth)" target="_blank">GET {{ baseNetworkUrl }}/api/depth?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of open offer prices for a single market.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketDepth" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="bisqMarketsHloc">
<ng-template ngbPanelTitle>
<span>GET Market HLOC</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketHloc)" target="_blank">GET {{ baseNetworkUrl }}/api/hloc?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides hi/low/open/close data for a given market. This can be used to generate a candlestick chart.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketHloc" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="bisqMarketsMarkets">
<ng-template ngbPanelTitle>
<span>GET Markets</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.markets)" target="_blank">GET {{ baseNetworkUrl }}/api/markets</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of available markets.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.markets" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="bisqMarketsOffers">
<ng-template ngbPanelTitle>
<span>GET Market Offers</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketOffers)" target="_blank">GET {{ baseNetworkUrl }}/api/offers?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of open offer details for a single market.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketOffers" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="bisqMarketsTicker">
<ng-template ngbPanelTitle>
<span>GET Market Ticker</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketTicker)" target="_blank">GET {{ baseNetworkUrl }}/api/ticker?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides 24 hour price ticker for single market or all markets</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketTicker" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="bisqMarketsTrades">
<ng-template ngbPanelTitle>
<span>GET Market Trades</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketTrades)" target="_blank">GET {{ baseNetworkUrl }}/api/trades?market=[:market]&limit=[:limit]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of completed trades for a single market.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketTrades" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="bisqMarketsVolumes">
<ng-template ngbPanelTitle>
<span>GET Market Volumes</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketVolumes)" target="_blank">GET {{ baseNetworkUrl }}/api/volumes?basecurrency=[:basecurrency]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides periodic volume data in terms of base currency for one or all markets.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketVolumes" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li *ngIf="network.val === 'bisq'" [ngbNavItem]="1">
<a ngbNavLink i18n="api-docs.tab.bsq|API Docs tab for BSQ">General</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
<ngb-panel id="bisqStats">
<ng-template ngbPanelTitle>
<span>GET Stats</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.stats)" target="_blank'" target="_blank">GET {{ baseNetworkUrl }}/api/stats</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns statistics about all Bisq transactions.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.stats" [network]="network.val"></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink i18n="api-docs.tab.addresses|API Docs tab for Addresses">Addresses</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
<ngb-panel id="address">
<ng-template ngbPanelTitle>
<span>GET Address</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.address)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about an address. Available fields: <code>address</code>, <code>chain_stats</code>, and <code>mempool_stats</code>. {{ '{' }}chain,mempool{{ '}' }}_stats each contain an object with <code>tx_count</code>, <code>funded_txo_count</code>, <code>funded_txo_sum</code>, <code>spent_txo_count</code>, and <code>spent_txo_sum</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.address" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressTransactions">
<ng-template ngbPanelTitle>
<span>GET Address Transactions</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressTransactions)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using <code>:last_seen_txid</code> (see below).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressTransactions" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressTransactionsChain">
<ng-template ngbPanelTitle>
<span>GET Address Transactions Chain</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressTransactionsChain)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs/chain</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressTransactionsChain" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressTransactionsMempool">
<ng-template ngbPanelTitle>
<span>GET Address Transactions Mempool</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressTransactionsMempool)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs/mempool</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get unconfirmed transaction history for the specified address/scripthash. Returns up to 50 transactions (no paging).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressTransactionsMempool" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="addressUTXO">
<ng-template ngbPanelTitle>
<span>GET Address UTXO</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressUTXO)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/utxo</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: <code>txid</code>, <code>vout</code>, <code>value</code>, and <code>status</code> (with the status of the funding tx).<ng-container *ngIf="network.val === 'liquid'">There is also a <code>valuecommitment</code> field that may appear in place of <code>value</code>, plus the following additional fields: <code>asset</code>/<code>assetcommitment</code>, <code>nonce</code>/<code>noncecommitment</code>, <code>surjection_proof</code>, and <code>range_proof</code>.</ng-container></div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressUTXO" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li *ngIf="network.val === 'liquid'" [ngbNavItem]="3">
<a ngbNavLink i18n="api-docs.tab.assets|API Docs tab for Assets">Assets</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
<ngb-panel id="assets">
<ng-template ngbPanelTitle>
<span>GET Assets</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.assets)" target="_blank">GET /liquid/api/asset/:asset_id</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns information about a Liquid asset.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assets" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="assetTransactions">
<ng-template ngbPanelTitle>
<span>GET Asset Transactions</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<a [href]="wrapUrl(network.val, code.assetTransactions)" target="_blank">GET /liquid/api/asset/:asset_id/txs[/mempool|/chain]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns transactions associated with the specified Liquid asset. For the network's native asset, returns a list of peg in, peg out, and burn transactions. For user-issued assets, returns a list of issuance, reissuance, and burn transactions. Does not include regular transactions transferring this asset.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assetTransactions" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="assetSupply">
<ng-template ngbPanelTitle>
<span>GET Asset Supply</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.assetSupply)" target="_blank">GET /liquid/api/asset/:asset_id/supply[/decimal]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get the current total supply of the specified asset. For the native asset (L-BTC), this is calculated as [chain,mempool]_stats.peg_in_amount - [chain,mempool]_stats.peg_out_amount - [chain,mempool]_stats.burned_amount. For issued assets, this is calculated as [chain,mempool]_stats.issued_amount - [chain,mempool]_stats.burned_amount. Not available for assets with blinded issuances. If /decimal is specified, returns the supply as a decimal according to the asset's divisibility. Otherwise, returned in base units.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assetSupply" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li [ngbNavItem]="4">
<a ngbNavLink i18n="api-docs.tab.blocks|API Docs tab for Blocks">Blocks</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
<ngb-panel id="block">
<ng-template ngbPanelTitle>
<span>GET Block</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.block)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about a block. Available fields: <code>id</code>, <code>height</code>, <code>version</code>, <code>timestamp</code>, <code>bits</code>, <code>nonce</code>, <code>merkle_root</code>, <code>tx_count</code>, <code>size</code>, <code>weight</code>,<ng-container *ngIf="network.val === 'liquid'"> <code>proof</code>,</ng-container> and <code>previousblockhash</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.block" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockHeader">
<ng-template ngbPanelTitle>
<span>GET Block Header</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockHeader)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/header</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the hex-encoded block header.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockHeader" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockHeight">
<ng-template ngbPanelTitle>
<span>GET Block Height</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockHeader)" target="_blank">GET {{ baseNetworkUrl }}/api/block-height/:height</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the hash of the block currently at <code>:height</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockHeight" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockRaw">
<ng-template ngbPanelTitle>
<span>GET Block Raw</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<a [href]="wrapUrl(network.val, code.blockRaw)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/raw</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the raw block representation in binary.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockRaw" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockStatus">
<ng-template ngbPanelTitle>
<span>GET Block Status</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="title">Get Block Status</div>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockStatus)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/status</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the confirmation status of a block. Available fields: <code>in_best_chain</code> (boolean, false for orphaned blocks), <code>next_best</code> (the hash of the next block, only available for blocks in the best chain).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockStatus" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="blockTipHeight">
<ng-template ngbPanelTitle>
<span>GET Block Tip Height</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTipHeight)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/tip/height</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the height of the last block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTipHeight" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTipHash">
<ng-template ngbPanelTitle>
<span>GET Block Tip Hash</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTipHash)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/tip/hash</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the hash of the last block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTipHash" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTxId">
<ng-template ngbPanelTitle>
<span>GET Block Transaction ID</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTxId)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txid/:index</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the transaction at index <code>:index</code> within the specified block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTxId" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTxIds">
<ng-template ngbPanelTitle>
<span>GET Block Transaction IDs</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTxIds)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txids</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a list of all txids in the block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTxIds" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blockTxs">
<ng-template ngbPanelTitle>
<span>GET Block Transactions</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTxs)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txs[/:start_index]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a list of transactions in the block (up to 25 transactions beginning at <code>start_index</code>). Transactions returned here do not have the <code>status</code> field, since all the transactions share the same block and confirmation status.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTxs" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="blocks">
<ng-template ngbPanelTitle>
<span>GET Blocks</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blocks)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks[/:start_height]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blocks" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val === 'bisq'" id="blocks">
<ng-template ngbPanelTitle>
<span>GET Blocks</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blocksBisq)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/:index/:length</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blocksBisq" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="5">
<a ngbNavLink i18n="api-docs.tab.fees|API Docs tab for Fees">Fees</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark">
<ngb-panel id="feeMempoolBlocks">
<ng-template ngbPanelTitle>
<span>GET Mempool Blocks Fees</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.feeMempoolBlocks)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/fees/mempool-blocks</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.fees.mempool-blocks|API Docs for /api/v1/fees/mempool-blocks">Returns current mempool as projected blocks.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.feeMempoolBlocks" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="feeRecommended">
<ng-template ngbPanelTitle>
<span>GET Recommended Fees</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.feeRecommended)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/fees/recommended</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.fees.recommended|API Docs for /api/v1/fees/recommended">Returns our currently suggested fees for new transactions.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.feeRecommended" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="6">
<a ngbNavLink i18n="api-docs.tab.mempool|API Docs tab for Mempool">Mempool</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
<ngb-panel id="mempool">
<ng-template ngbPanelTitle>
<span>GET Mempool</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.mempool)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.mempool.mempool|API Docs for /api/mempool">Returns current mempool backlog statistics.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.mempool" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="mempoolTxs">
<ng-template ngbPanelTitle>
<span>GET Mempool Transactions IDs</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.mempoolTxs)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool/txids</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.mempool.txids|API Docs for /api/mempool/txids">Get the full list of txids in the mempool as an array. The order of the txids is arbitrary and does not match bitcoind.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.mempoolTxs" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="mempoolRecent">
<ng-template ngbPanelTitle>
<span>GET Mempool Recent</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.mempoolRecent)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool/recent</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.mempool.recent|API Docs for /api/mempool/recent">Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: <code>txid</code>, <code>fee</code>, <code>vsize</code>, and <code>value</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.mempoolRecent" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li [ngbNavItem]="7">
<a ngbNavLink i18n="api-docs.tab.transactions|API Docs tab for Transactions">Transactions</a>
<ng-template ngbNavContent>
<ngb-accordion [closeOthers]="true" animated="true" type="dark" >
<ngb-panel *ngIf="network.val !== 'bisq'" id="cpfp">
<ng-template ngbPanelTitle>
<span>GET Children Pay for Parent</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionCpfp)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/cpfp/:txid</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.fees.cpfp|API Docs for /api/v1/fees/cpfp">Returns the ancestors and the best descendant fees for a transaction.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionCpfp" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel id="transaction">
<ng-template ngbPanelTitle>
<span>GET Transaction</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<a [href]="wrapUrl(network.val, code.transaction)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about a transaction. Available fields: <code>txid</code>, <code>version</code>, <code>locktime</code>, <code>size</code>, <code>weight</code>, <code>fee</code>, <code>vin</code>, <code>vout</code>, and <code>status</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transaction" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionHex">
<ng-template ngbPanelTitle>
<span>GET Transaction Hex</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionHex)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/hex</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a transaction serialized as hex.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionHex" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq' && network.val !== 'liquid'" id="transactionMerkleBlockProof">
<ng-template ngbPanelTitle>
<span>GET Transaction Merkleblock Proof</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionMerkleBlockProof)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/merkleblock-proof</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a merkle inclusion proof for the transaction using <a href="https://bitcoin.org/en/glossary/merkle-block">bitcoind's merkleblock</a> format.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionMerkleBlockProof" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionMerkleProof">
<ng-template ngbPanelTitle>
<span>GET Transaction Merkle Proof</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionMerkleProof)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/merkle-proof</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a merkle inclusion proof for the transaction using <a href="https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle">Electrum's blockchain.transaction.get_merkle format.</a></div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionMerkleProof" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionOutspend">
<ng-template ngbPanelTitle>
<span>GET Transaction Outspend</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionOutspend)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/outspend/:vout</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the spending status of a transaction output. Available fields: <code>spent</code> (boolean), <code>txid</code> (optional), <code>vin</code> (optional), and <code>status</code> (optional, the status of the spending tx).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionOutspend" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionOutspends">
<ng-template ngbPanelTitle>
<span>GET Transaction Outspends</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionOutspends)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/outspends</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the spending status of all transaction outputs.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionOutspends" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionRaw">
<ng-template ngbPanelTitle>
<span>GET Transaction Raw</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionRaw)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/raw</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a transaction as binary data.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionRaw" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="transactionStatus">
<ng-template ngbPanelTitle>
<span>GET Transaction Status</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionStatus)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/status</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the confirmation status of a transaction. Available fields: <code>confirmed</code> (boolean), <code>block_height</code> (optional), and <code>block_hash</code> (optional).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionStatus" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val === 'bisq'" id="transactionsBisq">
<ng-template ngbPanelTitle>
<span>GET Transactions</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="title">Get Mempool Txids</div>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionsBisq)" target="_blank">GET {{ baseNetworkUrl }}/api/txs/:index/:length</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns :length of latest Bisq transactions, starting from :index.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionsBisq" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
<ngb-panel *ngIf="network.val !== 'bisq'" id="postTransaction">
<ng-template ngbPanelTitle>
<span>POST Transaction</span>
</ng-template>
<ng-template ngbPanelContent>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<div>POST /api/tx</div>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The <code>txid</code> will be returned on success.</div>
</div>
<app-code-template [method]="'post'" [hostname]="hostname" [code]="code.transactionPost" [network]="network.val" ></app-code-template>
</ng-template>
</ngb-panel>
</ngb-accordion>
</ng-template>
</li>
<li *ngIf="network.val !== 'bisq'" [ngbNavItem]="8">
<a ngbNavLink i18n="api-docs.tab.websocket|API Docs tab for Websocket">Websocket</a>
<ng-template ngbNavContent >
<div class="websocket">
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
{{ wrapUrl(network.val, code.websocket, true) }}
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.websocket.websocket">Default push: <code>{{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }}</code> to express what you want pushed. Available: <code>blocks</code>, <code>mempool-blocks</code>, <code>live-2h-chart</code>, and <code>stats</code>.<br><br>Push transactions related to address: <code>{{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }}</code> to receive all new transactions containing that address as input or output. Returns an array of transactions. <code>address-transactions</code> for new mempool transactions, and <code>block-transactions</code> for new block confirmed transactions.</div>
</div>
<app-code-template [method]="'websocket'" [hostname]="hostname" [code]="code.websocket" [network]="network.val" ></app-code-template>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<br>
<div class="text-center">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
</div>
</div>
</ng-container>

View File

@@ -0,0 +1,76 @@
.text-small {
font-size: 12px;
}
code {
background-color: #1d1f31;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
}
tr {
white-space: inherit;
}
.nowrap {
white-space: nowrap;
}
li.nav-item {
width: 100%;
@media (min-width: 676px){
width: auto;
}
}
.nav-tabs .nav-link.active {
border-bottom: 1px solid #fff;
@media (min-width: 676px){
border-bottom: 1px solid #11131f;
}
}
.code-tab {
width: auto;
margin: 20px auto 10px;
li.nav-item {
width: auto;
}
}
.code {
.tab-content {
padding: 0px;
}
.nav-tabs .nav-link.active {
border-bottom: 1px solid #11131f;
}
.subtitle {
display: flex;
justify-content: space-between;
}
}
.description {
margin-top: 20px;
}
.title {
font-weight: bold;
color: #ffffff;
font-size: 1.25rem;
}
.subtitle {
font-weight: bold;
}
.divider {
width: 100%;
margin: 30px auto;
height: 1px;
background: #333;
}
.websocket {
padding: 15px;
}
.difficulty {
padding: 15px;
}

View File

@@ -7,7 +7,7 @@
<pre><code [innerText]="wrapCurlTemplate(code)"></code></pre>
</ng-template>
</li>
<li ngbNavItem *ngIf="network !== 'liquidtestnet'">
<li ngbNavItem>
<a ngbNavLink>CommonJS</a>
<ng-template ngbNavContent>
<div class="subtitle"><ng-container i18n="API Docs code example">Code Example</ng-container> <app-clipboard [text]="wrapCommonJS(code)"></app-clipboard></div>
@@ -18,7 +18,7 @@
</ng-template>
</li>
<li ngbNavItem>
<a ngbNavLink *ngIf="network !== 'liquidtestnet'">ES Module</a>
<a ngbNavLink>ES Module</a>
<ng-template ngbNavContent>
<div class="subtitle"><ng-container i18n="API Docs install lib">Install Package</ng-container> <app-clipboard [text]="wrapImportTemplate()"></app-clipboard></div>
<div class="links">

View File

@@ -26,7 +26,7 @@ export class CodeTemplateComponent implements OnInit {
if (this.network === 'bisq') {
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-bisq-js`;
}
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.network === 'liquid') {
npmLink = `https://github.com/mempool/mempool.js/tree/main/npm-liquid-js`;
}
return npmLink;
@@ -37,7 +37,7 @@ export class CodeTemplateComponent implements OnInit {
if (this.network === 'bisq') {
npmLink = `https://www.npmjs.org/package/@mempool/bisq.js`;
}
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.network === 'liquid') {
npmLink = `https://www.npmjs.org/package/@mempool/liquid.js`;
}
return npmLink;
@@ -50,7 +50,7 @@ export class CodeTemplateComponent implements OnInit {
} else {
codeText = codeText.replace('%{0}', 'bitcoin');
}
if(['', 'main', 'liquid', 'bisq', 'liquidtestnet'].includes(this.network)) {
if(['', 'main', 'liquid', 'bisq'].includes(this.network)) {
codeText = codeText.replace('mempoolJS();', `mempoolJS({
hostname: '${document.location.hostname}'
});`);
@@ -119,7 +119,7 @@ export class CodeTemplateComponent implements OnInit {
if (this.network === 'signet') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleSignet.esModule);
}
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.network === 'liquid') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleLiquid.esModule);
}
if (this.network === 'bisq') {
@@ -157,17 +157,13 @@ init();`;
if (this.network === 'signet') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleSignet.esModule);
}
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.network === 'liquid') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleLiquid.esModule);
}
if (this.network === 'bisq') {
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleBisq.esModule);
}
if (code.noWrap) {
return codeText;
}
let importText = `<script src="https://mempool.space/mempool.js"></script>`;
if (this.env.BASE_MODULE === 'bisq') {
importText = `<script src="https://bisq.markets/bisq.js"></script>`;
@@ -178,10 +174,10 @@ init();`;
let resultHtml = '<pre id="result"></pre>';
if (this.method === 'websocket') {
resultHtml = `<h2>Blocks</h2><pre id="result-blocks">Waiting for data</pre><br>
<h2>Mempool Info</h2><pre id="result-mempool-info">Waiting for data</pre><br>
<h2>Transactions</h2><pre id="result-transactions">Waiting for data</pre><br>
<h2>Mempool Blocks</h2><pre id="result-mempool-blocks">Waiting for data</pre><br>`;
resultHtml = `<pre id="result-blocks"></pre>
<pre id="result-mempool-info"></pre>
<pre id="result-transactions"></pre>
<pre id="result-mempool-blocks"></pre>`;
}
return `<!DOCTYPE html>
@@ -240,9 +236,6 @@ yarn add @mempool/liquid.js`;
if (this.network === 'liquid') {
return this.replaceCurlPlaceholder(code.codeTemplate.curl, code.codeSampleLiquid);
}
if (this.network === 'liquidtestnet') {
return this.replaceCurlPlaceholder(code.codeTemplate.curl, code.codeSampleLiquidTestnet);
}
if (this.network === 'bisq') {
return this.replaceCurlPlaceholder(code.codeTemplate.curl, code.codeSampleBisq);
}
@@ -265,9 +258,6 @@ yarn add @mempool/liquid.js`;
if (this.network === 'liquid') {
return code.codeSampleLiquid.response;
}
if (this.network === 'liquidtestnet') {
return code.codeSampleLiquidTestnet.response;
}
if (this.network === 'bisq') {
return code.codeSampleBisq.response;
}
@@ -303,18 +293,10 @@ yarn add @mempool/liquid.js`;
return `curl -X POST -sSLd "${text}"`;
}
return `curl -sSL "${this.hostname}/${this.network}${text}"`;
} else if (this.env.BASE_MODULE === 'liquid') {
if (this.method === 'post') {
if (this.network !== 'liquid') {
text = text.replace('/api', `/${this.network}/api`);
}
return `curl -X POST -sSLd "${text}"`;
}
return ( this.network === 'liquid' ? `curl -sSL "${this.hostname}${text}"` : `curl -sSL "${this.hostname}/${this.network}${text}"` );
} else {
return `curl -sSL "${this.hostname}${text}"`;
}
if (this.env.BASE_MODULE !== 'mempool') {
return `curl -sSL "${this.hostname}${text}"`;
}
}
}

View File

@@ -1,14 +1,11 @@
<div class="container-xl">
<div class="title-asset">
<h1 i18n="asset|Liquid Asset page title">Asset</h1>
<div class="tx-link">
<a [routerLink]="['/asset/' | relativeUrl, assetString]">
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ assetString }}</span>
</a>
<app-clipboard [text]="assetString"></app-clipboard>
</div>
</div>
<h1 style="float: left;" i18n="asset|Liquid Asset page title">Asset</h1>
<a [routerLink]="['/asset/' | relativeUrl, assetString]" style="line-height: 56px; margin-left: 10px;">
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ assetString }}</span>
</a>
<app-clipboard [text]="assetString"></app-clipboard>
<br>
<div class="clearfix"></div>
@@ -31,7 +28,7 @@
<td i18n="asset.issuer|Liquid Asset issuer">Issuer</td>
<td><a target="_blank" href="{{ 'http://' + assetContract[0] }}">{{ assetContract[0] }}</a></td>
</tr>
<tr *ngIf="asset.issuance_txin">
<tr *ngIf="!isNativeAsset">
<td i18n="asset.issuance-tx|Liquid Asset issuance TX">Issuance TX</td>
<td><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></td>
</tr>
@@ -42,15 +39,15 @@
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr *ngIf="isNativeAsset && asset.chain_stats.peg_in_amount">
<tr *ngIf="isNativeAsset">
<td i18n="asset.pegged-in|Liquid Asset pegged-in amount">Pegged in</td>
<td>{{ formatAmount(asset.chain_stats.peg_in_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="isNativeAsset && asset.chain_stats.peg_out_amount">
<tr *ngIf="isNativeAsset">
<td i18n="asset.pegged-out|Liquid Asset pegged-out amount">Pegged out</td>
<td>{{ formatAmount(asset.chain_stats.peg_out_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="asset.chain_stats.issued_amount">
<tr *ngIf="!isNativeAsset">
<td i18n="asset.issued-amount|Liquid Asset issued amount">Issued amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.issued_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
@@ -58,11 +55,11 @@
<td i18n="asset.burned-amount|Liquid Asset burned amount">Burned amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.burned_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="asset.chain_stats.issued_amount">
<tr *ngIf="!isNativeAsset">
<td i18n="asset.circulating-amount|Liquid Asset circulating amount">Circulating amount</td>
<td *ngIf="!blindedIssuance; else confidentialTd">{{ formatAmount(asset.chain_stats.issued_amount - asset.chain_stats.burned_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
<tr *ngIf="isNativeAsset && asset.chain_stats.peg_in_amount">
<tr *ngIf="isNativeAsset">
<td i18n="asset.circulating-amount|Liquid Asset circulating amount">Circulating amount</td>
<td>{{ formatAmount(asset.chain_stats.peg_in_amount - asset.chain_stats.burned_amount - asset.chain_stats.peg_out_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
</tr>
@@ -75,14 +72,12 @@
<br>
<div class="title-tx">
<h2>
<ng-template [ngIf]="transactions?.length" i18n="asset.M_of_N">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }}&nbsp;</ng-template>
<ng-template [ngIf]="isNativeAsset && network === 'liquid'" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
<ng-template #defaultAsset i18n="Default asset transactions title">Issuance and Burn Transactions</ng-template>
</h2>
</div>
<h2>
<ng-template [ngIf]="transactions?.length" i18n="asset.M_of_N">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }}&nbsp;</ng-template>
<ng-template [ngIf]="isNativeAsset" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
<ng-template #defaultAsset i18n="Default asset transactions title">Issuance and Burn Transactions</ng-template>
</h2>
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
<div class="text-center">

View File

@@ -20,33 +20,4 @@
margin-top: 20px;
margin-right: 0px;
}
}
h1 {
margin: 0px;
padding: 0px;
margin-right: 15px;
@media (min-width: 576px) {
float: left;
}
}
.tx-link {
display: block;
height: 100%;
top: 9px;
position: relative;
@media (min-width: 576px) {
top: 11px;
}
@media (min-width: 768px) {
top: 17px;
}
}
.title-tx {
h2 {
line-height: 1;
margin-bottom: 10px;
}
}
}

View File

@@ -20,7 +20,7 @@ import { moveDec } from 'src/app/bitcoin.utils';
})
export class AssetComponent implements OnInit, OnDestroy {
network = '';
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
nativeAssetId = environment.nativeAssetId;
asset: Asset;
blindedIssuance: boolean;

View File

@@ -10,18 +10,17 @@
</ng-container>
</a>
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
<img src="./resources/bisq-logo.png" style="width: 25px; height: 25px;" class="mr-1">
</button>
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage" ngbDropdownItem class="mainnet"><img src="./resources/bitcoin-logo.png" style="width: 30px;" class="mr-1"> Mainnet</a>
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + '/signet'" ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet"><img src="./resources/signet-logo.png" style="width: 30px;" class="mr-1"> Signet</a>
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + '/testnet'" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><img src="./resources/testnet-logo.png" style="width: 30px;" class="mr-1"> Testnet</a>
<a href="https://mempool.space" ngbDropdownItem class="mainnet"><img src="./resources/bitcoin-logo.png" style="width: 30px;" class="mr-1"> Mainnet</a>
<a href="https://mempool.space/signet" ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet"><img src="./resources/signet-logo.png" style="width: 30px;" class="mr-1"> Signet</a>
<a href="https://mempool.space/testnet" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><img src="./resources/testnet-logo.png" style="width: 30px;" class="mr-1"> Testnet</a>
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
<button ngbDropdownItem class="mainnet active" routerLink="/"><img src="./resources/bisq-logo.png" style="width: 30px;" class="mr-1"> Bisq</button>
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</a>
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + '/testnet'" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet"><img src="./resources/liquidtestnet-logo.png" style="width: 30px;" class="mr-1"> Liquid Testnet</a>
<a href="https://liquid.network" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid"><img src="./resources/liquid-logo.png" style="width: 30px;" class="mr-1"> Liquid</a>
</div>
</div>
@@ -39,8 +38,8 @@
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'file-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/docs']" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
<li class="nav-item mr-2" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/api' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cogs']" [fixedWidth]="true" i18n-title="master-page.api" title="API"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>

View File

@@ -102,10 +102,6 @@ nav {
background-color: #116761;
}
.liquidtestnet.active {
background-color: #494a4a;
}
.testnet.active {
background-color: #1d486f;
}

View File

@@ -1,7 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Env, StateService } from '../../services/state.service';
import { Observable } from 'rxjs';
import { LanguageService } from 'src/app/services/language.service';
@Component({
selector: 'app-bisq-master-page',
@@ -13,17 +12,14 @@ export class BisqMasterPageComponent implements OnInit {
navCollapsed = false;
env: Env;
isMobile = window.innerWidth <= 767.98;
urlLanguage: string;
constructor(
private stateService: StateService,
private languageService: LanguageService,
) { }
ngOnInit() {
this.env = this.stateService.env;
this.connectionState$ = this.stateService.connectionState$;
this.urlLanguage = this.languageService.getLanguageForUrl();
}
collapse(): void {

View File

@@ -2,8 +2,8 @@
<div class="title-block" id="block">
<h1>
<ng-template [ngIf]="blockHeight === 0"><ng-container i18n="@@2303359202781425764">Genesis</ng-container>
<span class="next-previous-blocks">
<ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis
<div class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
</a>
@@ -11,11 +11,10 @@
<span placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
</span>
</span>
</div>
</ng-template>
<ng-template [ngIf]="blockHeight" i18n="shared.block-title">Block <ng-container *ngTemplateOutlet="blockTemplateContent"></ng-container></ng-template>
<ng-template #blockTemplateContent>
<span class="next-previous-blocks">
<ng-template [ngIf]="blockHeight" i18n="block.block"> Block
<div class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
</a>
@@ -29,7 +28,7 @@
<span *ngIf="!showPreviousBlocklink" placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
</span>
</span>
</div>
</ng-template>
</h1>
@@ -81,12 +80,12 @@
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
<tr>
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees"><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat></span></td>
<td *ngIf="network !== 'liquid'; else liquidTotalFees"><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="fees * 100000000" digitsInfo="1.0-0"></app-fiat></span></td>
<ng-template #liquidTotalFees>
<td><app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount>&nbsp; <app-fiat [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat></td>
<td>{{ fees * 100000000 | number }} L-sat (<app-fiat [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>)</td>
</ng-template>
</tr>
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<tr *ngIf="network !== 'liquid'">
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td>
<app-amount [satoshis]="(blockSubsidy + fees) * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat></span>
@@ -98,7 +97,7 @@
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
</tr>
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<tr *ngIf="network !== 'liquid'">
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
<td><span class="skeleton-loader"></span></td>
</tr>
@@ -125,7 +124,7 @@
<td class="td-width" i18n="transaction.version">Version</td>
<td>{{ block.version | decimal2hex }} <span *ngIf="displayTaprootStatus() && hasTaproot(block.version)" class="badge badge-success ml-1" >Taproot</span></td>
</tr>
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<tr *ngIf="network !== 'liquid'">
<td i18n="block.bits">Bits</td>
<td>{{ block.bits | decimal2hex }}</td>
</tr>
@@ -136,7 +135,7 @@
</tbody>
</table>
</div>
<div class="col-sm" *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
<div class="col-sm" *ngIf="network !== 'liquid'">
<table class="table table-borderless table-striped">
<tbody>
<tr>
@@ -163,7 +162,7 @@
</div>
<div #blockTxTitle id="block-tx-title" class="block-tx-title">
<h2>
<h2 class="float-left">
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
@@ -176,14 +175,14 @@
<app-transactions-list [transactions]="transactions"></app-transactions-list>
<ng-template [ngIf]="isLoadingTransactions">
<div class="text-center mb-4" class="tx-skeleton">
<div class="text-center mb-4 mt-3">
<div class="header-bg box">
<div class="header-bg box" style="padding: 10px; margin-bottom: 10px;">
<span class="skeleton-loader"></span>
</div>
<div class="header-bg box">
<div class="row">
<div class="row" style="height: 107px;">
<div class="col-sm">
<span class="skeleton-loader"></span>
</div>
@@ -200,7 +199,7 @@
<div class="progress-bar progress-darklight" role="progressbar" [ngStyle]="{'width': txsLoadingStatus + '%' }"></div>
</div>
</ng-container>
</div>
</ng-template>
<ngb-pagination class="pagination-container float-right" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
@@ -208,7 +207,7 @@
</ng-template>
<ng-template [ngIf]="isLoadingBlock && !error">
<div class="box">
<div class="row">
<div class="col-sm">

View File

@@ -18,7 +18,7 @@
}
.fiat {
display: block;
display: block;
font-size: 13px;
@media (min-width: 768px) {
font-size: 14px;
@@ -40,7 +40,10 @@
h1 {
margin: 0px;
padding: 0px;
line-height: 1;
@media (min-width: 576px) {
float: left;
margin-right: 10px;
}
a {
&:hover, &:focus{
text-decoration: none;;
@@ -84,23 +87,32 @@ h1 {
}
.block-tx-title {
display: flex;
justify-content: space-between;
flex-direction: column;
margin-top: -15px;
position: relative;
padding-top: 10px;
display: block;
text-align: right;
margin-top: -30px;
@media (min-width: 550px) {
margin-top: 1rem;
flex-direction: row;
margin-top: 0px;
padding-top: 10px;
}
h2 {
line-height: 1;
display: inline-block;
float: left;
line-height: 1.6;
margin: 0;
margin-bottom: -15px;
padding-right: 10px;
padding-top: 15px;
position: relative;
padding-bottom: 10px;
top: -22px;
width: auto;
@media (min-width: 550px) {
padding-bottom: 0px;
align-self: end;
padding-top: 0px;
top: 0px;
}
@media (min-width: 768px) {
padding-top: 5px;
line-height: 1;
}
}
}
@@ -110,41 +122,22 @@ h1 {
}
.next-previous-blocks {
font-size: 28px;
font-size: 36px;
display: inline-block;
@media (min-width: 768px) {
font-size: 36px;
}
vertical-align: bottom;
a {
color: #1ad8f4;
&:hover, &:focus {
color: #09a3ba;
display: inline-block;
// transform: scale(1.2);
// transition: 150ms all ease-in-out;
}
}
}
.disable {
font-size: 28px;
font-size: 36px;
color: #393e5c73;
@media (min-width: 768px) {
font-size: 36px;
}
}
.tx-skeleton {
margin-top: 10px;
margin-bottom: 10px;
.header-bg {
&:first-child {
padding: 10px;
margin-bottom: 10px;
}
&:nth-child(2) {
.row {
height: 107px;
}
}
}
}
}

View File

@@ -8,7 +8,6 @@ import { Observable, of, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { SeoService } from 'src/app/services/seo.service';
import { WebsocketService } from 'src/app/services/websocket.service';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
@Component({
selector: 'app-block',
@@ -52,7 +51,6 @@ export class BlockComponent implements OnInit, OnDestroy {
private stateService: StateService,
private seoService: SeoService,
private websocketService: WebsocketService,
private relativeUrlPipe: RelativeUrlPipe,
) { }
ngOnInit() {
@@ -196,7 +194,7 @@ export class BlockComponent implements OnInit, OnDestroy {
if (this.showNextBlocklink) {
this.navigateToNextBlock();
} else {
this.router.navigate([this.relativeUrlPipe.transform('/mempool-block'), '0']);
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/mempool-block/', '0']);
}
}
});
@@ -212,7 +210,7 @@ export class BlockComponent implements OnInit, OnDestroy {
}
setBlockSubsidy() {
if (this.network === 'liquid' || this.network === 'liquidtestnet') {
if (this.network === 'liquid') {
this.blockSubsidy = 0;
return;
}
@@ -279,13 +277,13 @@ export class BlockComponent implements OnInit, OnDestroy {
return;
}
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight - 2);
this.router.navigate([this.relativeUrlPipe.transform('/block/'),
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/',
block ? block.id : this.block.previousblockhash], { state: { data: { block, blockHeight: this.nextBlockHeight - 2 } } });
}
navigateToNextBlock() {
const block = this.latestBlocks.find((b) => b.height === this.nextBlockHeight);
this.router.navigate([this.relativeUrlPipe.transform('/block/'),
this.router.navigate([(this.network && this.stateService.env.BASE_MODULE === 'mempool' ? '/' + this.network : '') + '/block/',
block ? block.id : this.nextBlockHeight], { state: { data: { block, blockHeight: this.nextBlockHeight } } });
}

View File

@@ -1,8 +1,7 @@
<div class="blocks-container blockchain-blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
<div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
<a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink">&nbsp;</a>
<div class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
</div>
@@ -26,7 +25,7 @@
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="transition" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
</div>
<ng-template #loadingBlocksTemplate>
<ng-template #loadingBlocksTemplate >
<div class="blocks-container">
<div class="flashing">
<div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn" >

View File

@@ -15,10 +15,6 @@
text-decoration: none;
}
.blockLink.disabled {
pointer-events: none;
}
.mined-block {
position: absolute;
top: 0px;
@@ -115,7 +111,7 @@
.flashing {
animation: opacityPulse 2s ease-out;
animation-iteration-count: infinite;
animation-iteration-count: infinite;
opacity: 1;
}
@@ -123,4 +119,4 @@
0% {opacity: 0.7;}
50% {opacity: 1.0;}
100% {opacity: 0.7;}
}
}

View File

@@ -1,9 +1,8 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { Block } from 'src/app/interfaces/electrs.interface';
import { StateService } from 'src/app/services/state.service';
import { Router } from '@angular/router';
import { specialBlocks } from 'src/app/app.constants';
@Component({
selector: 'app-blockchain-blocks',
@@ -12,7 +11,7 @@ import { specialBlocks } from 'src/app/app.constants';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
specialBlocks = specialBlocks;
network = '';
blocks: Block[] = [];
emptyBlocks: Block[] = this.mountEmptyBlocks();
@@ -36,19 +35,18 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
'': ['#9339f4', '#105fb0'],
bisq: ['#9339f4', '#105fb0'],
liquid: ['#116761', '#183550'],
'liquidtestnet': ['#494a4a', '#272e46'],
testnet: ['#1d486f', '#183550'],
signet: ['#6f1d5d', '#471850'],
};
constructor(
public stateService: StateService,
private stateService: StateService,
private router: Router,
private cd: ChangeDetectorRef,
) { }
ngOnInit() {
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
if (this.stateService.network === 'liquid') {
this.feeRounding = '1.0-1';
}
this.emptyBlocks.forEach((b) => this.emptyBlockStyles.push(this.getStyleForEmptyBlock(b)));

View File

@@ -18,11 +18,6 @@
.blockchain-wrapper {
overflow: hidden;
height: 250px;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
}
.position-container {
@@ -31,7 +26,7 @@
top: 75px;
}
.position-container.liquid, .position-container.liquidtestnet {
.position-container.liquid {
left: 420px;
}
@@ -39,7 +34,7 @@
.position-container {
left: 95%;
}
.position-container.liquid, .position-container.liquidtestnet {
.position-container.liquid {
left: 50%;
}
.position-container.loading {

View File

@@ -1,3 +0,0 @@
.btn-link {
padding: 0.25rem 0 0.1rem 0.5rem;
}

View File

@@ -1,76 +0,0 @@
<ng-template [ngIf]="network.val !== 'bisq' && network.val !== 'liquid' && network.val !== 'liquidtestnet'">
<p>General</p>
<a [routerLink]="['./']" fragment="get-difficulty-adjustment" (click)="collapseItem.toggle()">GET Difficulty Adjustment</a>
</ng-template>
<ng-template [ngIf]="network.val === 'bisq'">
<p>Markets</p>
<a [routerLink]="['./']" fragment="get-market-currencies" (click)="collapseItem.toggle()">GET Market Currencies</a>
<a [routerLink]="['./']" fragment="get-market-depth" (click)="collapseItem.toggle()">GET Market Depth</a>
<a [routerLink]="['./']" fragment="get-market-hloc" (click)="collapseItem.toggle()">GET Market HLOC</a>
<a [routerLink]="['./']" fragment="get-markets" (click)="collapseItem.toggle()">GET Markets</a>
<a [routerLink]="['./']" fragment="get-market-offers" (click)="collapseItem.toggle()">GET Market Offers</a>
<a [routerLink]="['./']" fragment="get-market-ticker" (click)="collapseItem.toggle()">GET Market Ticker</a>
<a [routerLink]="['./']" fragment="get-market-trades" (click)="collapseItem.toggle()">GET Market Trades</a>
<a [routerLink]="['./']" fragment="get-market-volumes" (click)="collapseItem.toggle()">GET Market Volumes</a>
</ng-template>
<ng-template [ngIf]="network.val === 'bisq'">
<p>General</p>
<a [routerLink]="['./']" fragment="get-stats" (click)="collapseItem.toggle()">GET Stats</a>
</ng-template>
<p>Addresses</p>
<a [routerLink]="['./']" fragment="get-address" (click)="collapseItem.toggle()">GET Address</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions" (click)="collapseItem.toggle()">GET Address Transactions</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions-chain" (click)="collapseItem.toggle()">GET Address Transactions Chain</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions-mempool" (click)="collapseItem.toggle()">GET Address Transactions Mempool</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-utxo" (click)="collapseItem.toggle()">GET Address UTXO</a>
<ng-template [ngIf]="network.val === 'liquid' || network.val === 'liquidtestnet'">
<p>Assets</p>
<a [routerLink]="['./']" fragment="get-asset" (click)="collapseItem.toggle()">GET Asset</a>
<a [routerLink]="['./']" fragment="get-asset-transactions" (click)="collapseItem.toggle()">GET Asset Transactions</a>
<a [routerLink]="['./']" fragment="get-asset-supply" (click)="collapseItem.toggle()">GET Asset Supply</a>
<a [routerLink]="['./']" fragment="get-asset-icons" (click)="collapseItem.toggle()">GET Asset Icons</a>
<a [routerLink]="['./']" fragment="get-asset-icon" (click)="collapseItem.toggle()">GET Asset Icon</a>
</ng-template>
<p>Blocks</p>
<a [routerLink]="['./']" fragment="get-block" (click)="collapseItem.toggle()">GET Block</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-header" (click)="collapseItem.toggle()">GET Block Header</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-height" (click)="collapseItem.toggle()">GET Block Height</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-raw" (click)="collapseItem.toggle()">GET Block Raw</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-status" (click)="collapseItem.toggle()">GET Block Status</a>
<a [routerLink]="['./']" fragment="get-block-tip-height" (click)="collapseItem.toggle()">GET Block Tip Height</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-tip-hash" (click)="collapseItem.toggle()">GET Block Tip Hash</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transaction-id" (click)="collapseItem.toggle()">GET Block Transaction ID</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transaction-ids" (click)="collapseItem.toggle()">GET Block Transaction IDs</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transactions" (click)="collapseItem.toggle()">GET Block Transactions</a>
<a [routerLink]="['./']" fragment="get-blocks" (click)="collapseItem.toggle()">GET Blocks</a>
<ng-template [ngIf]="network.val !== 'bisq'">
<p>Fees</p>
<a [routerLink]="['./']" fragment="get-mempool-blocks-fees" (click)="collapseItem.toggle()">GET Mempool Blocks Fees</a>
<a [routerLink]="['./']" fragment="get-recommended-fees" (click)="collapseItem.toggle()">GET Recommended Fees</a>
</ng-template>
<ng-template [ngIf]="network.val !== 'bisq'">
<p>Mempool</p>
<a [routerLink]="['./']" fragment="get-mempool" (click)="collapseItem.toggle()">GET Mempool</a>
<a [routerLink]="['./']" fragment="get-mempool-transaction-ids" (click)="collapseItem.toggle()">GET Mempool Transaction IDs</a>
<a [routerLink]="['./']" fragment="get-mempool-recent" (click)="collapseItem.toggle()">GET Mempool Recent</a>
</ng-template>
<p>Transactions</p>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-cpfp" (click)="collapseItem.toggle()">GET Children Pay for Parent</a>
<a [routerLink]="['./']" fragment="get-transaction" (click)="collapseItem.toggle()">GET Transaction</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-hex" (click)="collapseItem.toggle()">GET Transaction Hex</a>
<a *ngIf="network.val !== 'bisq' && network.val !== 'liquid' && network.val !== 'liquidtestnet'" [routerLink]="['./']" fragment="get-transaction-merkleblock-proof" (click)="collapseItem.toggle()">GET Transaction Merkleblock Proof</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-merkle-proof" (click)="collapseItem.toggle()">GET Transaction Merkle Proof</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspend" (click)="collapseItem.toggle()">GET Transaction Outspend</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspends" (click)="collapseItem.toggle()">GET Transaction Outspends</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-raw" (click)="collapseItem.toggle()">GET Transaction Raw</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-status" (click)="collapseItem.toggle()">GET Transaction Status</a>
<a *ngIf="network.val === 'bisq'" [routerLink]="['./']" fragment="get-transactions" (click)="collapseItem.toggle()">GET Transactions</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="post-transaction" (click)="collapseItem.toggle()">POST Transaction</a>

View File

@@ -1,17 +0,0 @@
p {
color: #4a68b9;
font-weight: 700;
margin: 10px 0;
margin: 15px 0 10px 0;
}
p:first-child {
margin-top: 0
}
a {
color: #fff;
text-decoration: none;
display: block;
margin: 5px 0;
}

View File

@@ -1,18 +0,0 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-api-docs-nav',
templateUrl: './api-docs-nav.component.html',
styleUrls: ['./api-docs-nav.component.scss']
})
export class ApiDocsNavComponent implements OnInit {
@Input() network: any;
@Input() collapseItem: any = { toggle: () => {} };
constructor() { }
ngOnInit(): void {
}
}

View File

@@ -1,705 +0,0 @@
<ng-container *ngIf="{ val: network$ | async } as network">
<div class="container-xl text-left">
<div id="restAPI" *ngIf="restTabActivated">
<div id="doc-nav-desktop" class="hide-on-mobile" [ngClass]="desktopDocsNavPosition">
<app-api-docs-nav [network]="{ val: network$ | async }"></app-api-docs-nav>
</div>
<div class="doc-content">
<p class="hide-on-mobile no-bottom-space">Reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">API service</ng-container>.</p>
<div id="doc-nav-mobile" class="hide-on-desktop" *ngIf="showFloatingDocsNav">
<button type="button" class="btn btn-outline-primary" (click)="collapse.toggle()" [attr.aria-expanded]="mobileMenuOpen" aria-controls="collapseExample"><fa-icon [icon]="['fas', 'list-ul']" [fixedWidth]="true"></fa-icon> {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} REST API Overview</button>
<div #collapse="ngbCollapse" [(ngbCollapse)]="mobileMenuOpen">
<div class="card">
<div class="card-body">
<app-api-docs-nav [collapseItem]="collapse" [network]="{ val: network$ | async }"></app-api-docs-nav>
</div>
</div>
</div>
</div>
<div id="mobile-top-doc-nav" #mobileFixedApiNav class="hide-on-desktop"><app-api-docs-nav [network]="{ val: network$ | async }"></app-api-docs-nav></div>
<div class="api-category" *ngIf="network.val !== 'bisq' && network.val !== 'liquid' && network.val !== 'liquidtestnet'">
<div class="endpoint-container" id="get-difficulty-adjustment">
<a class="section-header" [routerLink]="['./']" fragment="get-difficulty-adjustment">GET Difficulty Adjustment <span>General</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.difficulty)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/difficulty-adjustment</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about difficulty adjustment.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.difficulty" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category" *ngIf="network.val === 'bisq'">
<div class="endpoint-container" id="get-market-currencies">
<a class="section-header" [routerLink]="['./']" fragment="get-market-currencies">GET Market Currencies <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketsCurrencies)" target="_blank">GET {{ baseNetworkUrl }}/api/currencies</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of available currencies for a given base currency. </div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketsCurrencies" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-market-depth">
<a class="section-header" [routerLink]="['./']" fragment="get-market-depth">GET Market Depth <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketDepth)" target="_blank">GET {{ baseNetworkUrl }}/api/depth?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of open offer prices for a single market.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketDepth" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-market-hloc">
<a class="section-header" [routerLink]="['./']" fragment="get-market-hloc">GET Market HLOC <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketHloc)" target="_blank">GET {{ baseNetworkUrl }}/api/hloc?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides hi/low/open/close data for a given market. This can be used to generate a candlestick chart.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketHloc" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-markets">
<a class="section-header" [routerLink]="['./']" fragment="get-markets">GET Markets <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.markets)" target="_blank">GET {{ baseNetworkUrl }}/api/markets</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of available markets.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.markets" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-market-offers">
<a class="section-header" [routerLink]="['./']" fragment="get-market-offers">GET Market Offers <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketOffers)" target="_blank">GET {{ baseNetworkUrl }}/api/offers?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of open offer details for a single market.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketOffers" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-market-ticker">
<a class="section-header" [routerLink]="['./']" fragment="get-market-ticker">GET Market Ticker <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketTicker)" target="_blank">GET {{ baseNetworkUrl }}/api/ticker?market=[:market]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides 24 hour price ticker for single market or all markets</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketTicker" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-market-trades">
<a class="section-header" [routerLink]="['./']" fragment="get-market-trades">GET Market Trades <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketTrades)" target="_blank">GET {{ baseNetworkUrl }}/api/trades?market=[:market]&limit=[:limit]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides list of completed trades for a single market.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketTrades" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-market-volumes">
<a class="section-header" [routerLink]="['./']" fragment="get-market-volumes">GET Market Volumes <span>Markets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.marketVolumes)" target="_blank">GET {{ baseNetworkUrl }}/api/volumes?basecurrency=[:basecurrency]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Provides periodic volume data in terms of base currency for one or all markets.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.marketVolumes" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category" *ngIf="network.val === 'bisq'">
<div class="endpoint-container" id="get-stats">
<a class="section-header" [routerLink]="['./']" fragment="get-stats">GET Stats <span>General</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.stats)" target="_blank'" target="_blank">GET {{ baseNetworkUrl }}/api/stats</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns statistics about all Bisq transactions.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.stats" [network]="network.val"></app-code-template>
</div>
</div>
<div class="api-category">
<div class="endpoint-container" id="get-address">
<a class="section-header" [routerLink]="['./']" fragment="get-address">GET Address <span>Addresses</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.address)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about an address. Available fields: <code>address</code>, <code>chain_stats</code>, and <code>mempool_stats</code>. {{ '{' }}chain,mempool{{ '}' }}_stats each contain an object with <code>tx_count</code>, <code>funded_txo_count</code>, <code>funded_txo_sum</code>, <code>spent_txo_count</code>, and <code>spent_txo_sum</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.address" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-address-transactions">
<a class="section-header" [routerLink]="['./']" fragment="get-address-transactions">GET Address Transactions <span>Addresses</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressTransactions)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using <code>:last_seen_txid</code> (see below).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressTransactions" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-address-transactions-chain">
<a class="section-header" [routerLink]="['./']" fragment="get-address-transactions-chain">GET Address Transactions Chain <span>Addresses</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressTransactionsChain)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs/chain</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressTransactionsChain" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-address-transactions-mempool">
<a class="section-header" [routerLink]="['./']" fragment="get-address-transactions-mempool">GET Address Transactions Mempool <span>Addresses</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressTransactionsMempool)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/txs/mempool</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get unconfirmed transaction history for the specified address/scripthash. Returns up to 50 transactions (no paging).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressTransactionsMempool" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-address-utxo">
<a class="section-header" [routerLink]="['./']" fragment="get-address-utxo">GET Address UTXO <span>Addresses</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.addressUTXO)" target="_blank">GET {{ baseNetworkUrl }}/api/address/:address/utxo</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get the list of unspent transaction outputs associated with the address/scripthash. Available fields: <code>txid</code>, <code>vout</code>, <code>value</code>, and <code>status</code> (with the status of the funding tx).<ng-container *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'">There is also a <code>valuecommitment</code> field that may appear in place of <code>value</code>, plus the following additional fields: <code>asset</code>/<code>assetcommitment</code>, <code>nonce</code>/<code>noncecommitment</code>, <code>surjection_proof</code>, and <code>range_proof</code>.</ng-container></div>
</div>
<app-code-template [hostname]="hostname" [code]="code.addressUTXO" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category" *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'">
<div class="endpoint-container" id="get-asset">
<a class="section-header" [routerLink]="['./']" fragment="get-assets">GET Asset <span>Assets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.assets)" target="_blank">GET {{ baseNetworkUrl }}/api/asset/:asset_id</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns information about a Liquid asset.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assets" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-asset-transactions">
<a class="section-header" [routerLink]="['./']" fragment="get-asset-transactions">GET Asset Transactions <span>Assets</span></a>
<div class="endpoint">
<a [href]="wrapUrl(network.val, code.assetTransactions)" target="_blank">GET {{ baseNetworkUrl }}/api/asset/:asset_id/txs[/mempool|/chain]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns transactions associated with the specified Liquid asset. For the network's native asset, returns a list of peg in, peg out, and burn transactions. For user-issued assets, returns a list of issuance, reissuance, and burn transactions. Does not include regular transactions transferring this asset.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assetTransactions" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-asset-supply">
<a class="section-header" [routerLink]="['./']" fragment="get-asset-supply">GET Asset Supply <span>Assets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.assetSupply)" target="_blank">GET {{ baseNetworkUrl }}/api/asset/:asset_id/supply[/decimal]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Get the current total supply of the specified asset. For the native asset (L-BTC), this is calculated as [chain,mempool]_stats.peg_in_amount - [chain,mempool]_stats.peg_out_amount - [chain,mempool]_stats.burned_amount. For issued assets, this is calculated as [chain,mempool]_stats.issued_amount - [chain,mempool]_stats.burned_amount. Not available for assets with blinded issuances. If /decimal is specified, returns the supply as a decimal according to the asset's divisibility. Otherwise, returned in base units.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assetSupply" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-asset-icons">
<a class="section-header" [routerLink]="['./']" fragment="get-assets-icons">GET Asset Icons <span>Assets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.assetIcons)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/assets/icons</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div>Get all the Asset IDs that have icons.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assetIcons" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-asset-icon">
<a class="section-header" [routerLink]="['./']" fragment="get-asset-icon">GET Asset Icon <span>Assets</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.assetIcon)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/asset/:asset_id/icon</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div>Get the icon of the specified asset.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.assetIcon" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category">
<div class="endpoint-container" id="get-block">
<a class="section-header" [routerLink]="['./']" fragment="get-block">GET Block <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.block)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about a block. Available fields: <code>id</code>, <code>height</code>, <code>version</code>, <code>timestamp</code>, <code>bits</code>, <code>nonce</code>, <code>merkle_root</code>, <code>tx_count</code>, <code>size</code>, <code>weight</code>,<ng-container *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'"> <code>proof</code>,</ng-container> and <code>previousblockhash</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.block" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-header">
<a class="section-header" [routerLink]="['./']" fragment="get-block-header">GET Block Header <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockHeader)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/header</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the hex-encoded block header.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockHeader" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-height">
<a class="section-header" [routerLink]="['./']" fragment="get-block-height">GET Block Height <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockHeight)" target="_blank">GET {{ baseNetworkUrl }}/api/block-height/:height</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the hash of the block currently at <code>:height</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockHeight" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-raw">
<a class="section-header" [routerLink]="['./']" fragment="get-block-raw">GET Block Raw <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockRaw)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/raw</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the raw block representation in binary.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockRaw" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-status">
<a class="section-header" [routerLink]="['./']" fragment="get-block-status">GET Block Status <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockStatus)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/status</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the confirmation status of a block. Available fields: <code>in_best_chain</code> (boolean, false for orphaned blocks), <code>next_best</code> (the hash of the next block, only available for blocks in the best chain).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockStatus" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-block-tip-height">
<a class="section-header" [routerLink]="['./']" fragment="get-block-tip-height">GET Block Tip Height <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTipHeight)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/tip/height</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the height of the last block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTipHeight" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-tip-hash">
<a class="section-header" [routerLink]="['./']" fragment="get-block-tip-hash">GET Block Tip Hash <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTipHash)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/tip/hash</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the hash of the last block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTipHash" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-transaction-id">
<a class="section-header" [routerLink]="['./']" fragment="get-block-transaction-id">GET Block Transaction ID <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTxId)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txid/:index</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the transaction at index <code>:index</code> within the specified block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTxId" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-transaction-ids">
<a class="section-header" [routerLink]="['./']" fragment="get-block-transaction-ids">GET Block Transaction IDs <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTxIds)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txids</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a list of all txids in the block.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTxIds" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-block-transactions">
<a class="section-header" [routerLink]="['./']" fragment="get-block-transactions">GET Block Transactions <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blockTxs)" target="_blank">GET {{ baseNetworkUrl }}/api/block/:hash/txs[/:start_index]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a list of transactions in the block (up to 25 transactions beginning at <code>start_index</code>). Transactions returned here do not have the <code>status</code> field, since all the transactions share the same block and confirmation status.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blockTxs" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-blocks">
<a class="section-header" [routerLink]="['./']" fragment="get-blocks">GET Blocks <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blocks)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks[/:start_height]</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blocks" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val === 'bisq'" id="get-blocks">
<a class="section-header" [routerLink]="['./']" fragment="get-blocks">GET Blocks <span>Blocks</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.blocksBisq)" target="_blank">GET {{ baseNetworkUrl }}/api/blocks/:index/:length</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the 10 newest blocks starting at the tip or at <code>:start_height</code> if specified.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.blocksBisq" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category" *ngIf="network.val !== 'bisq'">
<div class="endpoint-container" id="get-mempool-blocks-fees">
<a class="section-header" [routerLink]="['./']" fragment="get-mempool-blocks-fees">GET Mempool Blocks Fees <span>Fees</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.feeMempoolBlocks)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/fees/mempool-blocks</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.fees.mempool-blocks|API Docs for /api/v1/fees/mempool-blocks">Returns current mempool as projected blocks.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.feeMempoolBlocks" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-recommended-fees">
<a class="section-header" [routerLink]="['./']" fragment="get-recommended-fees">GET Recommended Fees <span>Fees</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.feeRecommended)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/fees/recommended</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.fees.recommended|API Docs for /api/v1/fees/recommended">Returns our currently suggested fees for new transactions.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.feeRecommended" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category" *ngIf="network.val !== 'bisq'">
<div class="endpoint-container" id="get-mempool">
<a class="section-header" [routerLink]="['./']" fragment="get-mempool">GET Mempool <span>Fees</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.mempool)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.mempool.mempool|API Docs for /api/mempool">Returns current mempool backlog statistics.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.mempool" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-mempool-transaction-ids">
<a class="section-header" [routerLink]="['./']" fragment="get-mempool-transaction-ids">GET Mempool Transactions IDs <span>Fees</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.mempoolTxs)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool/txids</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.mempool.txids|API Docs for /api/mempool/txids">Get the full list of txids in the mempool as an array. The order of the txids is arbitrary and does not match bitcoind.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.mempoolTxs" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-mempool-recent">
<a class="section-header" [routerLink]="['./']" fragment="get-mempool-recent">GET Mempool Recent <span>Fees</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.mempoolRecent)" target="_blank">GET {{ baseNetworkUrl }}/api/mempool/recent</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.mempool.recent|API Docs for /api/mempool/recent">Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: <code>txid</code>, <code>fee</code>, <code>vsize</code>, and <code>value</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.mempoolRecent" [network]="network.val" ></app-code-template>
</div>
</div>
<div class="api-category">
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-cpfp">
<a class="section-header" [routerLink]="['./']" fragment="get-cpfp">GET Children Pay for Parent <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionCpfp)" target="_blank">GET {{ baseNetworkUrl }}/api/v1/cpfp/:txid</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.fees.cpfp|API Docs for /api/v1/fees/cpfp">Returns the ancestors and the best descendant fees for a transaction.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionCpfp" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" id="get-transaction">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction">GET Transaction <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transaction)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns details about a transaction. Available fields: <code>txid</code>, <code>version</code>, <code>locktime</code>, <code>size</code>, <code>weight</code>, <code>fee</code>, <code>vin</code>, <code>vout</code>, and <code>status</code>.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transaction" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-transaction-hex">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-hex">GET Transaction Hex <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionHex)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/hex</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a transaction serialized as hex.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionHex" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq' && network.val !== 'liquid' && network.val !== 'liquidtestnet'" id="get-transaction-merkleblock-proof">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-merkleblock-proof">GET Transaction Merkleblock Proof <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionMerkleBlockProof)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/merkleblock-proof</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a merkle inclusion proof for the transaction using <a href="https://bitcoin.org/en/glossary/merkle-block">bitcoind's merkleblock</a> format.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionMerkleBlockProof" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-transaction-merkle-proof">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-merkle-proof">GET Transaction Merkle Proof <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionMerkleProof)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/merkle-proof</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a merkle inclusion proof for the transaction using <a href="https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle">Electrum's blockchain.transaction.get_merkle format.</a></div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionMerkleProof" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-transaction-outspend">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-outspend">GET Transaction Outspend <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionOutspend)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/outspend/:vout</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the spending status of a transaction output. Available fields: <code>spent</code> (boolean), <code>txid</code> (optional), <code>vin</code> (optional), and <code>status</code> (optional, the status of the spending tx).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionOutspend" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-transaction-outspends">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-outspends">GET Transaction Outspends <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionOutspends)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/outspends</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the spending status of all transaction outputs.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionOutspends" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-transaction-raw">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-raw">GET Transaction Raw <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionRaw)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/raw</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns a transaction as binary data.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionRaw" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="get-transaction-status">
<a class="section-header" [routerLink]="['./']" fragment="get-transaction-status">GET Transaction Status <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionStatus)" target="_blank">GET {{ baseNetworkUrl }}/api/tx/:txid/status</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns the confirmation status of a transaction. Available fields: <code>confirmed</code> (boolean), <code>block_height</code> (optional), and <code>block_hash</code> (optional).</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionStatus" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val === 'bisq'" id="get-transactions">
<a class="section-header" [routerLink]="['./']" fragment="get-transactions">GET Transactions <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<a [href]="wrapUrl(network.val, code.transactionsBisq)" target="_blank">GET {{ baseNetworkUrl }}/api/txs/:index/:length</a>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Returns :length of latest Bisq transactions, starting from :index.</div>
</div>
<app-code-template [hostname]="hostname" [code]="code.transactionsBisq" [network]="network.val" ></app-code-template>
</div>
<div class="endpoint-container" *ngIf="network.val !== 'bisq'" id="post-transaction">
<a class="section-header" [routerLink]="['./']" fragment="post-transaction">POST Transaction <span>Transactions</span></a>
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
<div>POST {{ baseNetworkUrl }}/api/tx</div>
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n>Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The <code>txid</code> will be returned on success.</div>
</div>
<app-code-template [method]="'post'" [hostname]="hostname" [code]="code.transactionPost" [network]="network.val" ></app-code-template>
</div>
</div>
</div>
</div>
<div id="websocketAPI" *ngIf="!restTabActivated && ( network.val !== 'bisq' )">
<div class="api-category">
<div class="websocket">
<div class="endpoint">
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
{{ wrapUrl(network.val, code.websocket, true) }}
</div>
<div class="description">
<div class="subtitle" i18n>Description</div>
<div i18n="api-docs.websocket.websocket">Default push: <code>{{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }}</code> to express what you want pushed. Available: <code>blocks</code>, <code>mempool-blocks</code>, <code>live-2h-chart</code>, and <code>stats</code>.<br><br>Push transactions related to address: <code>{{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }}</code> to receive all new transactions containing that address as input or output. Returns an array of transactions. <code>address-transactions</code> for new mempool transactions, and <code>block-transactions</code> for new block confirmed transactions.</div>
</div>
<app-code-template [method]="'websocket'" [hostname]="hostname" [code]="code.websocket" [network]="network.val" ></app-code-template>
</div>
</div>
</div>
</div>
</ng-container>

View File

@@ -1,206 +0,0 @@
.text-small {
font-size: 12px;
}
code {
background-color: #1d1f31;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
}
tr {
white-space: inherit;
}
.nowrap {
white-space: nowrap;
}
li.nav-item {
width: 100%;
@media (min-width: 676px){
width: auto;
}
}
.no-bottom-space {
margin-bottom: 0;
}
.nav-tabs .nav-link.active {
border-bottom: 1px solid #fff;
@media (min-width: 676px){
border-bottom: 1px solid #11131f;
}
}
.code-tab {
width: auto;
margin: 20px auto 10px;
li.nav-item {
width: auto;
}
}
.code {
.tab-content {
padding: 0px;
}
.nav-tabs .nav-link.active {
border-bottom: 1px solid #11131f;
}
.subtitle {
display: flex;
justify-content: space-between;
}
}
.description {
margin-top: 20px;
}
.title {
font-weight: bold;
color: #ffffff;
font-size: 1.25rem;
}
.subtitle {
font-weight: bold;
}
.divider {
width: 100%;
margin: 30px auto;
height: 1px;
background: #333;
}
.difficulty {
padding: 15px;
}
#doc-nav-desktop {
width: 300px;
}
#doc-nav-desktop.relative {
float: left;
overflow: hidden;
}
#doc-nav-desktop.fixed {
float: unset;
position: fixed;
top: 20px;
overflow-y: auto;
height: calc(100vh - 50px);
scrollbar-color: #2d3348 #11131f;
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 3px;
}
::-webkit-scrollbar-track {
background: #11131f;
}
::-webkit-scrollbar-thumb {
background-color: #2d3348;
border-radius: 5px;
border: none;
}
.doc-content {
width: calc(100% - 330px);
float: right;
}
.endpoint-container:before {
display: block;
content: " ";
height: 1px;
margin-top: -1px;
visibility: hidden;
}
.endpoint-container .section-header {
display: block;
background-color: #2d3348;
color: #1bd8f4;
padding: 1rem 1.3rem 1rem 1.3rem;
font-weight: bold;
border-radius: 0.25rem;
margin: 20px 0 20px 0;
font-size: 24px;
position: relative;
}
.endpoint-container .section-header:hover {
text-decoration: none;
}
.endpoint-container .section-header span {
color: #fff;
background-color: #653b9c;
font-size: 12px;
text-transform: uppercase;
font-weight: 400;
padding: 8px 10px;
letter-spacing: 1px;
border-radius: 0.25rem;
font-family: monospace;
float: right;
}
#doc-nav-mobile {
position: fixed;
top: 20px;
width: calc(100% - 60px);
z-index: 100;
}
#doc-nav-mobile > div {
background-color: #2d3348;
z-index: 100;
border-radius: 0 0 0.5rem 0.5rem;
height: 55vh;
overflow-y: auto;
}
#doc-nav-mobile button {
width: 100%;
background-color: #105fb0;
color: #fff;
border-color: #105fb0;
border-radius: 0.5rem 0.5rem 0 0;
}
@media (max-width: 992px) {
.hide-on-mobile {
display: none;
}
.doc-content {
width: 100%;
}
.endpoint-container .section-header {
margin: 40px 0 70px 0;
}
.endpoint-container .section-header span {
float: none;
position: absolute;
top: unset;
left: 0;
bottom: -50px;
}
.endpoint-container:before {
height: 30px;
margin-top: -12px;
}
}
@media (min-width: 992px) {
.hide-on-desktop {
display: none;
}
}

Some files were not shown because too many files have changed in this diff Show More