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
138 changed files with 29762 additions and 34602 deletions

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

@@ -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
@@ -20,11 +20,11 @@ The following instructions are for a manual installation on Linux or FreeBSD. Th
## 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
@@ -41,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
```
@@ -54,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:
@@ -80,7 +80,7 @@ Install mempool dependencies from npm and build the backend:
```bash
# backend
cd backend
npm install --prod
npm install
npm run build
```
@@ -96,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,
@@ -116,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
}
}
```
@@ -156,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/mempool/ /var/www/
sudo rsync -av --delete dist/mempool /var/www/
```
## nginx + certbot
@@ -172,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/

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.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz",
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==",
"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.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz",
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A=="
"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

@@ -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

@@ -3,14 +3,13 @@ import { DB } from '../database';
import logger from '../logger';
import { Statistic, TransactionExtended, OptimizedStatistic } from '../mempool.interfaces';
import config from '../config';
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': [], '2y': [], '3y': []
'24h': [], '1w': [], '1m': [], '3m': [], '6m': [], '1y': [],
};
public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
@@ -49,8 +48,6 @@ class Statistics {
this.cache['3m'] = await this.$list3M();
this.cache['6m'] = await this.$list6M();
this.cache['1y'] = await this.$list1Y();
this.cache['2y'] = await this.$list2Y();
this.cache['3y'] = await this.$list3Y();
logger.debug('Statistics cache created');
}
@@ -85,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 (
(config.MEMPOOL.NETWORK === 'liquid' && (i === lastItem || transaction.effectiveFeePerVsize * 10 < logFees[i + 1]))
||
(config.MEMPOOL.NETWORK !== 'liquid' && (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 {
@@ -411,37 +403,10 @@ class Statistics {
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(120960);
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(181440);
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 {

View File

@@ -69,8 +69,7 @@ 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 });
@@ -143,7 +142,7 @@ class Server {
if (this.wss) {
websocketHandler.setWebsocketServer(this.wss);
}
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
if (config.MEMPOOL.NETWORK === 'liquid') {
blocks.setNewBlockCallback(async () => {
try {
await elementsParser.$parse();
@@ -170,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 });
@@ -218,8 +216,6 @@ class Server {
.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))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.get2YStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.get3YStatistics.bind(routes))
;
}
@@ -270,7 +266,7 @@ class Server {
;
}
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
if (config.MEMPOOL.NETWORK === 'liquid') {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
;

View File

@@ -52,14 +52,6 @@ class Routes {
res.json(statistics.getCache()['1y']);
}
public get2YStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['2y']);
}
public get3YStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['3y']);
}
public getInitData(req: Request, res: Response) {
try {
const result = websocketHandler.getInitData();
@@ -621,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);
@@ -638,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);
@@ -724,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;
@@ -778,29 +768,8 @@ 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);
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);
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 })

View File

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

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,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}

1
frontend/.gitignore vendored
View File

@@ -34,7 +34,6 @@ speed-measure-plugin.json
.history/*
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage

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

@@ -1,8 +1,8 @@
describe('Bisq', () => {
const baseModule = Cypress.env("BASE_MODULE");
const basePath = (baseModule === 'bisq') ? '' : '/bisq';
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 === 'mempool' || 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,7 +56,7 @@ 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('.card').should('have.length.at.least', 1);
@@ -67,7 +67,7 @@ describe('Bisq', () => {
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
@@ -76,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 = (baseModule === 'liquid') ? '' : '/liquid';
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 === 'mempool' || 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');
@@ -64,13 +58,13 @@ describe('Liquid', () => {
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);
@@ -78,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();
@@ -90,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', '');
@@ -118,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,7 +1,5 @@
import { emitMempoolInfo, dropWebSocket } from "../../support/websocket";
const baseModule = Cypress.env("BASE_MODULE");
describe('Mainnet', () => {
beforeEach(() => {
//cy.intercept('/sockjs-node/info*').as('socket');
@@ -22,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');
@@ -69,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('/');
@@ -111,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', () => {
@@ -271,7 +232,7 @@ describe('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');
@@ -308,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('/');
@@ -347,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();
@@ -429,6 +363,6 @@ describe('Mainnet', () => {
});
});
} 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) => {}

15210
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -53,18 +53,16 @@
"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.0.4",
"@angular/animations": "~13.0.3",
"@angular/cli": "~13.0.4",
"@angular/common": "~13.0.3",
"@angular/compiler": "~13.0.3",
"@angular/core": "~13.0.3",
"@angular/forms": "~13.0.3",
"@angular/localize": "^13.0.3",
"@angular/platform-browser": "~13.0.3",
"@angular/platform-browser-dynamic": "~13.0.3",
"@angular/platform-server": "~13.0.3",
"@angular/router": "~13.0.3",
"@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",
@@ -73,7 +71,7 @@
"@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",
@@ -84,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",
@@ -92,8 +90,10 @@
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular/compiler-cli": "~13.0.3",
"@angular/language-service": "~13.0.3",
"@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",
@@ -110,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`);
}
}
@@ -60,20 +61,4 @@ PROXY_CONFIG = [
}
];
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

@@ -21,17 +21,12 @@ import { TrademarkPolicyComponent } from './components/trademark-policy/trademar
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,
@@ -100,10 +95,6 @@ let routes: Routes = [
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -173,10 +164,6 @@ let routes: Routes = [
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -239,10 +226,6 @@ let routes: Routes = [
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -342,10 +325,6 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
path: '',
component: DashboardComponent
},
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: 'tx/:id',
component: TransactionComponent

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

@@ -57,7 +57,6 @@ import { TrademarkPolicyComponent } from './components/trademark-policy/trademar
import { StorageService } from './services/storage.service';
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
import { SponsorComponent } from './components/sponsor/sponsor.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
@NgModule({
declarations: [
@@ -99,7 +98,6 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
PrivacyPolicyComponent,
TrademarkPolicyComponent,
SponsorComponent,
PushTransactionComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),

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

@@ -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

@@ -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

@@ -12,7 +12,6 @@ import { BisqDashboardComponent } from './bisq-dashboard/bisq-dashboard.componen
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

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

@@ -34,9 +34,9 @@
<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" />
@@ -159,32 +159,20 @@
</a>
</div>
</div>
<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">
<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>
<div class="maintainers" *ngIf="contributors.core.length">
<h3 i18n="about.project_staff">Project Staff</h3>
<div class="wrapper">
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
<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>
</ng-container>
</div>
<div class="maintainers">
<h3 i18n="about.maintainers">Project Maintainers</h3>

View File

@@ -6,7 +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';
@Component({
selector: 'app-about',
@@ -17,7 +16,7 @@ import { map } from 'rxjs/operators';
export class AboutComponent implements OnInit {
backendInfo$: Observable<IBackendInfo>;
sponsors$: Observable<any>;
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;
@@ -38,14 +37,7 @@ export class AboutComponent implements OnInit {
this.websocketService.want(['blocks']);
this.sponsors$ = this.apiService.getDonation$();
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>
@@ -26,7 +25,7 @@
<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>
@@ -35,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>
@@ -51,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>
@@ -87,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>
@@ -116,7 +114,7 @@
</div>
<div class="w-100 d-block d-md-none"></div>
<div class="col">
</div>
</div>
</div>
@@ -124,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>
@@ -139,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[];
@@ -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

@@ -33,7 +33,7 @@
</li>
<li *ngIf="network.val === 'bisq'" [ngbNavItem]="0">
<a ngbNavLink i18n="Bisq All Markets">Markets</a>
<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" >

View File

@@ -3416,8 +3416,8 @@ export class ApiDocsComponent implements OnInit {
ws.addEventListener('message', function incoming({data}) {
const res = JSON.parse(data.toString());
if (res.block) {
document.getElementById("result-blocks").textContent = JSON.stringify(res.block, undefined, 2);
if (res.blocks) {
document.getElementById("result-blocks").textContent = JSON.stringify(res.blocks, undefined, 2);
}
if (res.mempoolInfo) {
document.getElementById("result-mempool-info").textContent = JSON.stringify(res.mempoolInfo, undefined, 2);
@@ -3425,8 +3425,8 @@ export class ApiDocsComponent implements OnInit {
if (res.transactions) {
document.getElementById("result-transactions").textContent = JSON.stringify(res.transactions, undefined, 2);
}
if (res["mempool-blocks"]) {
document.getElementById("result-mempool-blocks").textContent = JSON.stringify(res["mempool-blocks"], undefined, 2);
if (res.mempoolBlocks) {
document.getElementById("result-mempool-blocks").textContent = JSON.stringify(res.mempoolBlocks, undefined, 2);
}
});
`,
@@ -3439,8 +3439,8 @@ export class ApiDocsComponent implements OnInit {
ws.on("message", function incoming(data) {
const res = JSON.parse(data.toString());
if (res.block) {
console.log(res.block);
if (res.blocks) {
console.log(res.blocks);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
@@ -3448,8 +3448,8 @@ export class ApiDocsComponent implements OnInit {
if (res.transactions) {
console.log(res.transactions);
}
if (res["mempool-blocks"]) {
console.log(res["mempool-blocks"]);
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
}
});
`,

View File

@@ -174,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>

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>
@@ -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" [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

@@ -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>
@@ -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

@@ -1,6 +1,6 @@
<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)">
<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>
@@ -25,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

@@ -111,7 +111,7 @@
.flashing {
animation: opacityPulse 2s ease-out;
animation-iteration-count: infinite;
animation-iteration-count: infinite;
opacity: 1;
}
@@ -119,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();

View File

@@ -1,4 +1 @@
<div class="echarts" echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>
<div class="echarts" echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption"></div>

View File

@@ -1,4 +1,4 @@
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy } from '@angular/core';
import { formatDate } from '@angular/common';
import { EChartsOption } from 'echarts';
import { OnChanges } from '@angular/core';
@@ -7,14 +7,6 @@ import { StorageService } from 'src/app/services/storage.service';
@Component({
selector: 'app-incoming-transactions-graph',
templateUrl: './incoming-transactions-graph.component.html',
styles: [`
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 16px);
z-index: 100;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
@@ -26,7 +18,6 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
@Input() left: number | string = '0';
@Input() template: ('widget' | 'advanced') = 'widget';
isLoading = true;
mempoolStatsChartOption: EChartsOption = {};
mempoolStatsChartInitOption = {
renderer: 'svg'
@@ -38,23 +29,13 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
private storageService: StorageService,
) { }
ngOnInit() {
this.isLoading = true;
}
ngOnChanges(): void {
if (!this.data) {
return;
}
this.windowPreference = this.storageService.getValue('graphWindowPreference');
this.mountChart();
}
rendered() {
if (!this.data) {
return;
}
this.isLoading = false;
ngOnInit(): void {
this.mountChart();
}
mountChart(): void {

View File

@@ -1,4 +1 @@
<div class="echarts" echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions" (chartRendered)="rendered()"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>
<div class="echarts" echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions"></div>

View File

@@ -1,22 +1,14 @@
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
import { formatDate, formatNumber } from '@angular/common';
import { EChartsOption } from 'echarts';
@Component({
selector: 'app-lbtc-pegs-graph',
styles: [`
::ng-deep .tx-wrapper-tooltip-chart { width: 135px; }
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 16px);
z-index: 100;
}
`],
styles: [`::ng-deep .tx-wrapper-tooltip-chart { width: 135px; }`],
templateUrl: './lbtc-pegs-graph.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LbtcPegsGraphComponent implements OnInit, OnChanges {
export class LbtcPegsGraphComponent implements OnChanges {
@Input() data: any;
pegsChartOptions: EChartsOption;
@@ -25,7 +17,6 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
top: number | string = '20';
left: number | string = '50';
template: ('widget' | 'advanced') = 'widget';
isLoading = true;
pegsChartOption: EChartsOption = {};
pegsChartInitOption = {
@@ -36,10 +27,6 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
@Inject(LOCALE_ID) private locale: string,
) { }
ngOnInit() {
this.isLoading = true;
}
ngOnChanges() {
if (!this.data) {
return;
@@ -47,13 +34,6 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
this.pegsChartOptions = this.createChartOptions(this.data.series, this.data.labels);
}
rendered() {
if (!this.data) {
return;
}
this.isLoading = false;
}
createChartOptions(series: number[], labels: string[]): EChartsOption {
return {
grid: {

View File

@@ -72,14 +72,6 @@ li.nav-item {
width: 60%;
}
.navbar {
.dropdown {
.dropdown-toggle {
width: 62px;
}
}
}
@media (min-width: 576px) {
.navbar-brand {
width: 140px;
@@ -140,4 +132,4 @@ nav {
}
.navbar-dark .navbar-nav .nav-link {
color: #f1f1f1;
}
}

View File

@@ -1,7 +1,7 @@
<div class="container-xl" *ngIf="mempoolBlock$ | async as mempoolBlock">
<div class="title-block">
<h1>{{ ordinal$ | async }}</h1>
<h1 class="float-left">{{ ordinal$ | async }}</h1>
<button [routerLink]="['/' | relativeUrl]" class="btn btn-sm float-right">&#10005;</button>
</div>

View File

@@ -4,6 +4,13 @@
top: 5px;
}
.title-block {
color: #FFF;
padding-top: 20px;
padding-bottom: 3px;
border-top: 3px solid #FFF;
}
.fiat {
font-size: 13px;
display: inline-block;
@@ -24,9 +31,9 @@
h1 {
margin: 0px;
padding: 0px;
line-height: 1;
@media (min-width: 576px) {
float: left;
margin-right: 10px;
}
}

View File

@@ -1,8 +1,8 @@
<ng-container *ngIf="(loadingBlocks$ | async) === false; else loadingBlocks">
<div class="mempool-blocks-container" *ngIf="(timeAvg$ | async) as timeAvg;">
<div class="flashing">
<div class="flashing">
<ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn">
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]">
<a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink">&nbsp;</a>
<div class="block-body">
<div class="fees">

View File

@@ -18,7 +18,7 @@
.flashing {
animation: opacityPulse 2s ease-out;
animation-iteration-count: infinite;
animation-iteration-count: infinite;
opacity: 1;
}
@@ -60,7 +60,7 @@
}
.bitcoin-block::after {
content: '';
content: '';
width: 125px;
height: 24px;
position:absolute;
@@ -68,7 +68,7 @@
left: -20px;
background-color: #232838;
transform:skew(40deg);
transform-origin:top;
transform-origin:top;
}
.bitcoin-block::before {
@@ -78,18 +78,18 @@
position: absolute;
top: -12px;
left: -20px;
background-color: #191c27;
background-color: #191c27;
transform: skewY(50deg);
transform-origin: top;
transform-origin: top;
}
.mempool-block.bitcoin-block::after {
background-color: #403834;
background-color: #403834;
}
.mempool-block.bitcoin-block::before {
background-color: #2d2825;
background-color: #2d2825;
}
.black-background {
@@ -102,8 +102,8 @@
position: relative;
right: 75px;
top: 140px;
width: 0;
height: 0;
width: 0;
height: 0;
border-left: 35px solid transparent;
border-right: 35px solid transparent;
border-bottom: 35px solid #FFF;

View File

@@ -3,9 +3,8 @@ import { Subscription, Observable, fromEvent, merge, of, combineLatest, timer }
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
import { StateService } from 'src/app/services/state.service';
import { Router } from '@angular/router';
import { take, map, switchMap } from 'rxjs/operators';
import { take, map, switchMap, share } from 'rxjs/operators';
import { feeLevels, mempoolFeeColors } from 'src/app/app.constants';
import { specialBlocks } from 'src/app/app.constants';
@Component({
selector: 'app-mempool-blocks',
@@ -14,13 +13,12 @@ import { specialBlocks } from 'src/app/app.constants';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MempoolBlocksComponent implements OnInit, OnDestroy {
specialBlocks = specialBlocks;
mempoolBlocks: MempoolBlock[] = [];
mempoolEmptyBlocks: MempoolBlock[] = this.mountEmptyBlocks();
mempoolBlocks$: Observable<MempoolBlock[]>;
timeAvg$: Observable<number>;
loadingBlocks$: Observable<boolean>;
blocksSubscription: Subscription;
mempoolBlocksFull: MempoolBlock[] = [];
mempoolBlockStyles = [];
@@ -76,36 +74,26 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
fromEvent(window, 'resize')
)
.pipe(
switchMap(() => combineLatest([
this.stateService.blocks$.pipe(map(([block]) => block)),
this.stateService.mempoolBlocks$
.pipe(
map((mempoolBlocks) => {
if (!mempoolBlocks.length) {
return [{ index: 0, blockSize: 0, blockVSize: 0, feeRange: [0, 0], medianFee: 0, nTx: 0, totalFees: 0 }];
}
return mempoolBlocks;
}),
)
])),
map(([lastBlock, mempoolBlocks]) => {
mempoolBlocks.forEach((block, i) => {
block.index = this.blockIndex + i;
block.height = lastBlock.height + i + 1;
if (this.stateService.network === '') {
block.blink = specialBlocks[block.height] ? true : false;
}
});
switchMap(() => this.stateService.mempoolBlocks$),
map((blocks) => {
if (!blocks.length) {
return [{ index: 0, blockSize: 0, blockVSize: 0, feeRange: [0, 0], medianFee: 0, nTx: 0, totalFees: 0 }];
}
return blocks;
}),
map((blocks) => {
blocks.forEach((block, i) => {
block.index = this.blockIndex + i;
});
const stringifiedBlocks = JSON.stringify(blocks);
this.mempoolBlocksFull = JSON.parse(stringifiedBlocks);
this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
this.updateMempoolBlockStyles();
this.calculateTransactionPosition();
return this.mempoolBlocks;
})
);
const stringifiedBlocks = JSON.stringify(mempoolBlocks);
this.mempoolBlocksFull = JSON.parse(stringifiedBlocks);
this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
this.updateMempoolBlockStyles();
this.calculateTransactionPosition();
return this.mempoolBlocks;
})
);
this.timeAvg$ = timer(0, 1000)
.pipe(
@@ -130,7 +118,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
} else {
timeAvgMins += Math.abs(timeAvgDiff);
}
return timeAvgMins * 60 * 1000;
})
);

View File

@@ -1,4 +1 @@
<div echarts class="echarts" (chartInit)="onChartReady($event)" (chartRendered)="rendered()" [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>
<div echarts class="echarts" (chartInit)="onChartReady($event)" [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>

View File

@@ -12,14 +12,6 @@ import { feeLevels, chartColors } from 'src/app/app.constants';
@Component({
selector: 'app-mempool-graph',
templateUrl: './mempool-graph.component.html',
styles: [`
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 16px);
z-index: 100;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MempoolGraphComponent implements OnInit, OnChanges {
@@ -33,7 +25,6 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
@Input() template: ('widget' | 'advanced') = 'widget';
@Input() showZoom = true;
isLoading = true;
mempoolVsizeFeesData: any;
mempoolVsizeFeesOptions: EChartsOption;
mempoolVsizeFeesInitOptions = {
@@ -54,24 +45,14 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
) { }
ngOnInit(): void {
this.isLoading = true;
this.inverted = this.storageService.getValue('inverted-graph') === 'true';
}
ngOnChanges() {
if (!this.data) {
return;
}
this.windowPreference = this.storageService.getValue('graphWindowPreference');
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
this.mountFeeChart();
}
rendered() {
if (!this.data) {
return;
}
this.isLoading = false;
ngOnChanges() {
this.windowPreference = this.storageService.getValue('graphWindowPreference');
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
this.mountFeeChart();
}
onChartReady(myChart: any) {
@@ -90,6 +71,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
const labels = mempoolStats.map(stats => stats.added);
const finalArrayVByte = this.generateArray(mempoolStats);
// Only Liquid has lower than 1 sat/vb transactions
if (this.stateService.network !== 'liquid') {
finalArrayVByte.shift();
}
return {
labels: labels,
series: finalArrayVByte
@@ -99,7 +85,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
generateArray(mempoolStats: OptimizedMempoolStats[]) {
const finalArray: number[][] = [];
let feesArray: number[] = [];
let limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
const limitFeesTemplate = this.template === 'advanced' ? 28 : 21;
for (let index = limitFeesTemplate; index > -1; index--) {
feesArray = [];
mempoolStats.forEach((stats) => {
@@ -252,9 +238,6 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
</tr>`);
});
const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : '';
const titleRange = $localize`Range`;
const titleSize = $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`;
const titleSum = $localize`Sum`;
return `<div class="fees-wrapper-tooltip-chart ${classActive}">
<div class="title">
${params[0].axisValue}
@@ -265,9 +248,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
<table>
<thead>
<tr>
<th>${titleRange}</th>
<th>${titleSize}</th>
<th>${titleSum}</th>
<th>Range</th>
<th>Size</th>
<th>Sum</th>
<th></th>
</tr>
</thead>
@@ -362,10 +345,10 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
this.feeLimitIndex = i;
}
if (feeLevels[i] <= this.limitFee) {
if (this.stateService.network === 'liquid') {
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
if (i === 0) {
this.feeLevelsOrdered.push('0 - 1');
} else {
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
this.feeLevelsOrdered.push(`${feeLevels[i - 1]} - ${feeLevels[i]}`);
}
}
}

View File

@@ -5,53 +5,40 @@
<br><br>
<h2>Privacy Policy</h2>
<h6>Updated: November 18, 2021</h6>
<h6>Updated: August 2, 2021</h6>
<br><br>
<div class="text-left">
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, the <a href="https://bisq.markets/">bisq.markets</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from <a href="https://wq.apnic.net/static/search.html?query=AS142052">AS142052</a>.</p>
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
<h5>By accessing this Website, you agree to the following Privacy Policy:</h5>
<br>
<h4>TRUSTED THIRD PARTIES ARE SECURITY HOLES</h4>
<p>Out of respect for the Bitcoin community, this website does not use any third-party analytics, third-party trackers, or third-party cookies, and we do not share any private user data with third-parties. Additionally, to mitigate the risk of surveillance by malicious third-parties, we self-host this website on our own hardware and network infrastructure, so there are no "hosting companies" or "cloud providers" involved with the operation of this website.</p>
<br>
<ul>
<li>We do not use any third-party analytics, trackers, or cookies.</li>
<li>We do not share any private user data with third-parties.</li>
</ul>
<h4>TRUSTED FIRST PARTIES ARE ALSO SECURITY HOLES</h4>
<p>Out of respect for the Bitcoin community, this website does not use any first-party cookies, except to store your preferred language setting (if any). However, we do use minimal first-party analytics and logging as needed for the operation of this website, as follows:</p>
<ul>
<li>We use basic webserver logging (nginx) for sysadmin purposes, which collects your IP address along with the requests you make. These logs are deleted after 10 days, and we do not share this data with any third-party. To conceal your IP address from our webserver logs, we recommend that you use Tor Browser with our Tor v3 hidden service onion hostname.</li>
<br>
<li>We use a self-hosted statistics application (matomo) for analytics purposes, which collects your IP address along with the requests you make. Our matomo instance is configured to respect your privacy by redacting your IP address and other methods, and we do not share this data with any third-party. To conceal your activity from our analytics, we recommend that you use a privacy protecting browser extension that blocks matomo from being loaded.</li>
<li>Your IP address may be collected in our webserver logs used for sysadmin purposes.</li>
<li>Your IP address may be collected in our self-hosted statistics matomo app.</li>
</ul>
<br>
<h4>DON'T TRUST US, PROTECT YOUR PRIVACY</h4>
<h4>TRUST YOUR OWN SELF-HOSTED MEMPOOL EXPLORER</h4>
<ul>
<li>Use a Tor Browser or a privacy VPN service to hide your IP address from us.</li>
<li>Use a self-hosted instance of The Mempool Open Source Project&trade; on your own hardware.</li>
</ul>
<p>For maximum privacy, we recommend that you use your own self-hosted instance of The Mempool Open Source Project&trade; on your own hardware. You can easily install your own self-hosted instance of this website on a Raspberry Pi using a one-click installation method maintained by various Bitcoin fullnode distributions such as Umbrel, RaspiBlitz, MyNode, and RoninDojo. See our project's GitHub page for more details about self-hosting this website.</p>
<h4>IF YOU DONATE TO MEMPOOL.SPACE</h4>
<br>
<h4>DONATING TO MEMPOOL.SPACE</h4>
<p>If you donate to mempool.space, your payment information and your Twitter identity (if provided) will be collected in a database, which may be used to publicly display the sponsor profiles on <a href="https://mempool.space/about">mempool.space/about</a>. Thank you for supporting The Mempool Open Source Project.</p>
<br>
<ul>
<li>Your payment information, together with your Twitter identity (if provided) will be collected.</li>
<li>We display sponsor profiles publicly on <a href="https://mempool.space/about">mempool.space/about</a>.</li>
<li>Thank you :)</li>
</ul>
<p>EOF</p>

View File

@@ -1,12 +0,0 @@
<div class="container-xl">
<h1 i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</h1>
<form [formGroup]="pushTxForm" (submit)="pushTxForm.valid && postTx()" novalidate>
<div class="mb-3">
<textarea formControlName="txHash" class="form-control" rows="5" i18n-placeholder="transaction.hex" placeholder="Transaction Hex"></textarea>
</div>
<button [disabled]="isLoading" type="submit" class="btn btn-primary mr-2" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</button>
<p class="red-color d-inline">{{ error }}</p> <a *ngIf="txId" [routerLink]="['/tx/' | relativeUrl, txId]">{{ txId }}</a>
</form>
</div>

View File

@@ -1,48 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ApiService } from 'src/app/services/api.service';
@Component({
selector: 'app-push-transaction',
templateUrl: './push-transaction.component.html',
styleUrls: ['./push-transaction.component.scss']
})
export class PushTransactionComponent implements OnInit {
pushTxForm: FormGroup;
error: string = '';
txId: string = '';
isLoading = false;
constructor(
private formBuilder: FormBuilder,
private apiService: ApiService,
) { }
ngOnInit(): void {
this.pushTxForm = this.formBuilder.group({
txHash: ['', Validators.required],
});
}
postTx() {
this.isLoading = true;
this.error = '';
this.txId = '';
this.apiService.postTransaction$(this.pushTxForm.get('txHash').value)
.subscribe((result) => {
this.isLoading = false;
this.txId = result;
this.pushTxForm.reset();
},
(error) => {
if (typeof error.error === 'string') {
const matchText = error.error.match('"message":"(.*?)"');
this.error = matchText && matchText[1] || error.error;
} else if (error.message) {
this.error = error.message;
}
this.isLoading = false;
});
}
}

View File

@@ -1,5 +1,5 @@
import { Component, Input, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import * as QRCode from 'qrcode';
import { Component, Input, AfterViewInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import * as QRCode from 'qrcode/build/qrcode.js';
import { StateService } from 'src/app/services/state.service';
@Component({
@@ -23,7 +23,7 @@ export class QrcodeComponent implements AfterViewInit {
if (!this.stateService.isBrowser) {
return;
}
const opts: QRCode.QRCodeRenderersOptions = {
const opts = {
errorCorrectionLevel: 'H',
margin: 0,
color: {
@@ -31,6 +31,7 @@ export class QrcodeComponent implements AfterViewInit {
light: '#fff'
},
width: this.size,
height: this.size,
};
if (!this.data) {

View File

@@ -10,14 +10,14 @@ form {
@media (min-width: 576px) {
margin-top: 0px;
margin-left: 8px;
}
}
@media (min-width: 992px) {
width: 100%;
}
}
.btn-block {
width: 62px;
width: 58.55px;
}
.search-box-container {
@@ -37,4 +37,4 @@ form {
.btn {
width: 100px;
}
}
}

View File

@@ -25,7 +25,7 @@ export class SearchFormComponent implements OnInit {
regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100})$/;
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
regexTransaction = /^([a-fA-F0-9]{64}):?(\d+)?$/;
regexTransaction = /^[a-fA-F0-9]{64}$/;
regexBlockheight = /^[0-9]+$/;
@ViewChild('instance', {static: true}) instance: NgbTypeahead;
@@ -100,23 +100,22 @@ export class SearchFormComponent implements OnInit {
} else if (this.regexBlockhash.test(searchText) || this.regexBlockheight.test(searchText)) {
this.navigate('/block/', searchText);
} else if (this.regexTransaction.test(searchText)) {
const matches = this.regexTransaction.exec(searchText);
if (this.network === 'liquid') {
if (this.assets[matches[1]]) {
this.navigate('/asset/', matches[1]);
if (this.assets[searchText]) {
this.navigate('/asset/', searchText);
}
this.electrsApiService.getAsset$(matches[1])
this.electrsApiService.getAsset$(searchText)
.subscribe(
() => { this.navigate('/asset/', matches[1]); },
() => { this.navigate('/asset/', searchText); },
() => {
this.electrsApiService.getBlock$(matches[1])
this.electrsApiService.getBlock$(searchText)
.subscribe(
(block) => { this.navigate('/block/', matches[1], { state: { data: { block } } }); },
() => { this.navigate('/tx/', matches[0]); });
(block) => { this.navigate('/block/', searchText, { state: { data: { block } } }); },
() => { this.navigate('/tx/', searchText); });
}
);
} else {
this.navigate('/tx/', matches[0]);
this.navigate('/tx/', searchText);
}
} else {
this.isSearching = false;

View File

@@ -1,13 +1,3 @@
<ng-container *ngIf="specialEvent">
<div class="pyro">
<div class="before"></div>
<div class="after"></div>
</div>
<div class="warning-label">{{ eventName }}</div>
</ng-container>
<div *ngIf="countdown > 0" class="warning-label">{{ eventName }} in {{ countdown | number }} block{{ countdown === 1 ? '' : 's' }}!</div>
<div id="blockchain-container" dir="ltr">
<app-blockchain></app-blockchain>
</div>

View File

@@ -1,5 +1,3 @@
@use 'sass:math';
#blockchain-container {
position: relative;
overflow-x: scroll;
@@ -9,141 +7,5 @@
}
#blockchain-container::-webkit-scrollbar {
display: none;
}
.warning-label {
position: relative;
text-align: center;
font-weight: bold;
font-size: 12px;
padding: 6px 4px;
margin-top: -10px;
}
// Fireworks
$particles: 50;
$width: 500;
$height: 500;
// Create the explosion...
$box-shadow: ();
$box-shadow2: ();
@for $i from 0 through $particles {
$box-shadow: $box-shadow,
random($width) - math.div($width, 1.2) + px
random($height) - math.div($height, 1.2) + px
hsl(random(360), 100%, 50%);
$box-shadow2: $box-shadow2, 0 0 #fff
}
@mixin keyframes ($animationName) {
@-webkit-keyframes #{$animationName} {
@content;
}
@-moz-keyframes #{$animationName} {
@content;
}
@-o-keyframes #{$animationName} {
@content;
}
@-ms-keyframes #{$animationName} {
@content;
}
@keyframes #{$animationName} {
@content;
}
}
@mixin animation-delay ($settings) {
-moz-animation-delay: $settings;
-webkit-animation-delay: $settings;
-o-animation-delay: $settings;
-ms-animation-delay: $settings;
animation-delay: $settings;
}
@mixin animation-duration ($settings) {
-moz-animation-duration: $settings;
-webkit-animation-duration: $settings;
-o-animation-duration: $settings;
-ms-animation-duration: $settings;
animation-duration: $settings;
}
@mixin animation ($settings) {
-moz-animation: $settings;
-webkit-animation: $settings;
-o-animation: $settings;
-ms-animation: $settings;
animation: $settings;
}
@mixin transform ($settings) {
transform: $settings;
-moz-transform: $settings;
-webkit-transform: $settings;
-o-transform: $settings;
-ms-transform: $settings;
}
body {
margin:0;
padding:0;
background: #000;
overflow: hidden;
}
.pyro > .before, .pyro > .after {
z-index: 100;
position: absolute;
width: 5px;
height: 5px;
border-radius: 50%;
box-shadow: $box-shadow2;
@include animation((1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards));
}
.pyro > .after {
@include animation-delay((1.25s, 1.25s, 1.25s));
@include animation-duration((1.25s, 1.25s, 6.25s));
}
@include keyframes(bang) {
to {
box-shadow:$box-shadow;
}
}
@include keyframes(gravity) {
to {
@include transform(translateY(200px));
opacity: 0;
}
}
@include keyframes(position) {
0%, 19.9% {
margin-top: 10%;
margin-left: 40%;
}
20%, 39.9% {
margin-top: 40%;
margin-left: 30%;
}
40%, 59.9% {
margin-top: 20%;
margin-left: 70%
}
60%, 79.9% {
margin-top: 30%;
margin-left: 20%;
}
80%, 99.9% {
margin-top: 30%;
margin-left: 80%;
}
display: none;
}

View File

@@ -1,53 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { WebsocketService } from 'src/app/services/websocket.service';
import { StateService } from 'src/app/services/state.service';
import { specialBlocks } from 'src/app/app.constants';
import { takeLast } from 'rxjs/operators';
import { Component } from '@angular/core';
@Component({
selector: 'app-start',
templateUrl: './start.component.html',
styleUrls: ['./start.component.scss'],
})
export class StartComponent implements OnInit {
interval = 60;
colors = ['#5E35B1', '#ffffff'];
countdown = 0;
specialEvent = false;
eventName = '';
constructor(
private websocketService: WebsocketService,
private stateService: StateService,
) { }
ngOnInit() {
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
this.stateService.blocks$
.subscribe((blocks: any) => {
if (this.stateService.network !== '') {
return;
}
this.countdown = 0;
const block = blocks[0];
for (const sb in specialBlocks) {
const height = parseInt(sb, 10);
const diff = height - block.height;
if (diff > 0 && diff <= 1008) {
this.countdown = diff;
this.eventName = specialBlocks[sb].labelEvent;
}
}
if (specialBlocks[block.height]) {
this.specialEvent = true;
this.eventName = specialBlocks[block.height].labelEventCompleted;
setTimeout(() => {
this.specialEvent = false;
}, 60 * 60 * 1000);
}
});
}
export class StartComponent {
constructor() { }
}

View File

@@ -1,10 +1,18 @@
<div class="container-graph">
<div>
<div *ngIf="loading" class="loading">
<div class="text-center">
<h3 i18n="statistics.loading-graphs">Loading graphs...</h3>
<br>
<div class="spinner-border text-light"></div>
</div>
</div>
<div>
<div class="card mb-3">
<div class="card mb-3" *ngIf="mempoolStats.length">
<div class="card-header">
<i class="fa fa-area-chart"></i> <span i18n="statistics.memory-by-vBytes">Mempool by vBytes (sat/vByte)</span>
<form [formGroup]="radioGroupForm" class="formRadioGroup" (click)="saveGraphPreference()">
<div class="spinner-border text-light bootstrap-spinner" *ngIf="spinnerLoading"></div>
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'2h'" [routerLink]="['/graphs' | relativeUrl]" fragment="2h"> 2H (LIVE)
@@ -27,42 +35,45 @@
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'2y'" [routerLink]="['/graphs' | relativeUrl]" fragment="2y"> 2Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'3y'" [routerLink]="['/graphs' | relativeUrl]" fragment="3y"> 3Y
</label>
</div>
<div class="small-buttons">
<div ngbDropdown #myDrop="ngbDropdown">
<button class="btn btn-primary btn-sm" id="dropdownFees" ngbDropdownAnchor (click)="myDrop.toggle()">
<fa-icon [icon]="['fas', 'filter']" [fixedWidth]="true" i18n-title="statistics.component-filter.title" title="Filter"></fa-icon>
</button>
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
<ul>
<ng-template ngFor let-fee let-i="index" [ngForOf]="feeLevels">
<ng-template [ngIf]="fee <= 400">
<li (click)="filterFees(fee)" [class]="filterFeeIndex > fee ? 'inactive' : ''">
<div class="d-inline-block" ngbDropdown #myDrop="ngbDropdown">
<button class="btn btn-primary btn-sm ml-2" id="dropdownFees" ngbDropdownAnchor (click)="myDrop.toggle()">
<fa-icon [icon]="['fas', 'filter']" [fixedWidth]="true" i18n-title="statistics.component-filter.title" title="Filter"></fa-icon>
</button>
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
<ul>
<ng-template ngFor let-fee let-i="index" [ngForOf]="feeLevels">
<ng-template [ngIf]="fee === 1">
<li (click)="filterFees(fee)" [class]="filterFeeIndex > fee ? 'inactive' : ''">
<ng-template [ngIf]="inverted">
<span class="square" [ngStyle]="{'backgroundColor': chartColors[i]}"></span>
<span class="fee-text" >{{feeLevels[i]}} - {{ feeLevels[i + 1] }}</span>
</ng-template>
<ng-template [ngIf]="!inverted">
<span class="square" [ngStyle]="{'backgroundColor': chartColors[i - 1]}"></span>
<span class="fee-text" >{{feeLevels[i]}} - {{ feeLevels[i - 1] }}</span>
</ng-template>
<span class="fee-text" >0 - {{ fee }}</span>
</li>
</ng-template>
</ng-template>
</ul>
</div>
<ng-template [ngIf]="fee <= 500 && fee !== 1">
<li (click)="filterFees(fee)" [class]="filterFeeIndex > fee ? 'inactive' : ''">
<ng-template [ngIf]="inverted">
<span class="square" [ngStyle]="{'backgroundColor': chartColors[i]}"></span>
<span class="fee-text" >{{feeLevels[i - 1]}} - {{ fee }}</span>
</ng-template>
<ng-template [ngIf]="!inverted">
<span class="square" [ngStyle]="{'backgroundColor': chartColors[i - 1]}"></span>
<span class="fee-text" >{{feeLevels[i + 1]}} - {{ fee }}</span>
</ng-template>
</li>
</ng-template>
</ng-template>
</ul>
</div>
<button (click)="invertGraph()" class="btn btn-primary btn-sm"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button>
</div>
<button (click)="invertGraph()" class="btn btn-primary btn-sm ml-2 d-none d-md-inline"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button>
</form>
<div class="spinner-border text-light bootstrap-spinner" *ngIf="spinnerLoading && mempoolStats.length"></div>
</div>
<div class="card-body">
<div class="incoming-transactions-graph">
@@ -74,7 +85,7 @@
[height]="500"
[left]="65"
[right]="10"
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"
[data]="mempoolStats"
></app-mempool-graph>
</div>
</div>
@@ -82,7 +93,7 @@
</div>
<div>
<div class="card mb-3">
<div class="card mb-3" *ngIf="mempoolTransactionsWeightPerSecondData">
<div class="card-header">
<i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
</div>

View File

@@ -18,14 +18,12 @@
.bootstrap-spinner {
width: 22px;
height: 22px;
margin-right: 10px;
order: 2;
margin-left: 10px;
position: absolute;
top: 17px;
right: 25px;
@media (min-width: 830px) {
position: relative;
top: 0px;
right: 0px;
@media (min-width: 653px) {
margin-left: 0px;
order: 1;
}
}
@@ -36,18 +34,17 @@
}
}
.formRadioGroup {
.formRadioGroup{
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 830px) {
flex-direction: row;
@media (min-width: 653px) {
display: block;
float: right;
margin-top: 0px;
}
.btn-sm {
font-size: 9px;
@media (min-width: 830px) {
font-size: 10px;
@media (min-width: 768px) {
font-size: 14px;
}
}
@@ -109,48 +106,3 @@
}
}
}
.btn-group-toggle {
display: inline-flex;
@media (min-width: 830px) {
display: block;
}
}
.small-buttons {
width: 100%;
display: flex;
font-size: 14px;
margin: 7px 0px;
justify-content: space-between;
flex-direction: row-reverse;
@media (min-width: 830px) {
margin: 2px 0px;
width: auto;
flex-direction: row;
}
@media (min-width: 830px) {
margin: 0px 0px;
}
.btn {
width: 49.25%;
@media (min-width: 830px) {
width: auto;
}
}
.dropdown {
width: 49.25%;
display: flex;
@media (min-width: 830px) {
width: auto;
margin: 0px 5px;
}
}
#dropdownFees {
width: 100%;
@media (min-width: 830px) {
width: auto;
}
}
}

View File

@@ -72,7 +72,7 @@ export class StatisticsComponent implements OnInit {
this.route
.fragment
.subscribe((fragment) => {
if (['2h', '24h', '1w', '1m', '3m', '6m', '1y', '2y', '3y'].indexOf(fragment) > -1) {
if (['2h', '24h', '1w', '1m', '3m', '6m', '1y'].indexOf(fragment) > -1) {
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
}
});
@@ -104,13 +104,7 @@ export class StatisticsComponent implements OnInit {
if (this.radioGroupForm.controls.dateSpan.value === '6m') {
return this.apiService.list6MStatistics$();
}
if (this.radioGroupForm.controls.dateSpan.value === '1y') {
return this.apiService.list1YStatistics$();
}
if (this.radioGroupForm.controls.dateSpan.value === '2y') {
return this.apiService.list2YStatistics$();
}
return this.apiService.list3YStatistics$();
return this.apiService.list1YStatistics$();
})
)
.subscribe((mempoolStats: any) => {

View File

@@ -1,5 +1,10 @@
<div id="tv-wrapper">
<div class="tv-container">
<div *ngIf="mempoolStats.length === 0" class="loading">
<div class="spinner-border text-light"></div>
</div>
<div class="tv-container" *ngIf="mempoolStats.length">
<div class="chart-holder">
<app-mempool-graph
[template]="'advanced'"
@@ -7,7 +12,7 @@
[height]="600"
[left]="60"
[right]="10"
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"
[data]="mempoolStats"
[showZoom]="false"
></app-mempool-graph>
</div>

View File

@@ -16,8 +16,7 @@
}
.chart-holder {
position: relative;
height: 655px;
height: 650px;
width: 100%;
margin: 30px auto 0;
}

View File

@@ -9,18 +9,18 @@
</a>
</div>
<ng-container>
<h1 i18n="shared.transaction">Transaction</h1>
<div>
<div class="title">
<h1 i18n="shared.transaction">Transaction</h1>
</div>
<span class="tx-link float-left">
<div class="tx-link float-left">
<a [routerLink]="['/tx/' | relativeUrl, txId]">
<span class="d-inline d-lg-none">{{ txId | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ txId }}</span>
</a>
<app-clipboard [text]="txId"></app-clipboard>
</span>
<span class="grow"></span>
</div>
<div class="container-buttons">
<ng-template [ngIf]="tx?.status?.confirmed">
@@ -34,7 +34,7 @@
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
</ng-template>
</div>
</ng-container>
</div>
</div>
<div class="clearfix"></div>
@@ -192,58 +192,36 @@
<br>
<div class="title float-left">
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
</div>
<h2 class="float-left" i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
<button type="button" class="btn btn-outline-info btn-sm float-right" (click)="txList.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
<button type="button" class="btn btn-outline-info btn-sm float-right mr-1 mt-0 mt-md-2" (click)="txList.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
<div class="clearfix"></div>
<app-transactions-list #txList [transactions]="[tx]" [errorUnblinded]="errorUnblinded" [outputIndex]="outputIndex" [transactionPage]="true"></app-transactions-list>
<app-transactions-list #txList [transactions]="[tx]" [errorUnblinded]="errorUnblinded" [transactionPage]="true"></app-transactions-list>
<div class="title">
<h2 i18n="transaction.details">Details</h2>
</div>
<h2 class="text-left" i18n="transaction.details">Details</h2>
<div class="box">
<div class="row">
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td i18n="transaction.size|Transaction Size">Size</td>
<td [innerHTML]="'&lrm;' + (tx.size | bytes: 2)"></td>
</tr>
<tr>
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
<td [innerHTML]="'&lrm;' + (tx.weight / 4 | vbytes: 2)"></td>
</tr>
<tr>
<td i18n="transaction.weight|Transaction Weight">Weight</td>
<td [innerHTML]="'&lrm;' + (tx.weight | wuBytes: 2)"></td>
</tr>
</tbody>
</table>
</div>
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td i18n="transaction.version">Version</td>
<td [innerHTML]="'&lrm;' + (tx.version | number)"></td>
</tr>
<tr>
<td i18n="transaction.locktime">Locktime</td>
<td [innerHTML]="'&lrm;' + (tx.locktime | number)"></td>
</tr>
<tr>
<td i18n="transaction.hex">Transaction Hex</td>
<td><a target="_blank" href="{{ network === '' ? '' : '/' + network }}/api/tx/{{ txId }}/hex"><fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true"></fa-icon></a></td>
</tr>
</tbody>
</table>
</div>
</div>
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td i18n="transaction.size|Transaction Size">Size</td>
<td [innerHTML]="'&lrm;' + (tx.size | bytes: 2)"></td>
</tr>
<tr>
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
<td [innerHTML]="'&lrm;' + (tx.weight / 4 | vbytes: 2)"></td>
</tr>
<tr>
<td i18n="transaction.weight|Transaction Weight">Weight</td>
<td [innerHTML]="'&lrm;' + (tx.weight | wuBytes: 2)"></td>
</tr>
<tr>
<td i18n="transaction.hex">Transaction Hex</td>
<td><a target="_blank" href="{{ network === '' ? '' : '/' + network }}/api/tx/{{ txId }}/hex"><fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true"></fa-icon></a></td>
</tr>
</tbody>
</table>
</div>
</ng-template>
@@ -285,78 +263,41 @@
<br>
<div class="title">
<h2 i18n="transaction.inputs-and-outputs|Transaction inputs and outputs">Inputs & Outputs</h2>
</div>
<h2>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>
<br>
<div class="title">
<h2 i18n="transaction.details">Details</h2>
</div>
<h2 i18n="transaction.details">Details</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>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<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>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
</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>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
</ng-template>

View File

@@ -2,48 +2,53 @@
padding: 0.55rem;
}
.title-block {
color: #FFF;
padding-top: 20px;
border-top: 3px solid #FFF;
width: 100%;
padding-bottom: 30px;
display: flex;
flex-direction: column;
justify-content: space-between;
@media (min-width: 768px) {
padding-bottom: 0px;
}
}
h1{
margin-top: 2px;
margin-bottom: 0;
float: left;
margin-top: 2px;
@media (min-width: 768px){
margin-top: -8px;
}
}
.container-buttons {
text-align: right;
align-self: start;
width: auto;
margin-right: 15px;
right: 0;
position: absolute;
@media (min-width: 650px) {
right: auto;
margin-right: auto;
position: relative;
}
@media (min-width: 768px) {
align-self: center;
width: 100%;
@media (min-width: 850px) {
width: auto;
float: right;
}
}
.title-block {
flex-direction: column;
@media (min-width: 650px) {
flex-direction: row;
}
h1 {
margin: 0rem;
line-height: 1;
}
}
.tx-link {
display: flex;
margin-bottom: 0px;
margin-top: 8px;
@media (min-width: 650px) {
align-self: end;
margin-left: 15px;
margin-top: 0px;
margin-bottom: -3px;
}
display: block;
width: auto;
margin-bottom: 10px;
margin-left: 2px;
margin-top: 40px;
position: absolute;
@media (min-width: 768px) {
margin-bottom: 0px;
top: 1px;
position: relative;
margin-top: 14px;
margin-left: 10px;
position: relative;
text-align: left;
width: auto;
float: left;
}
}
@@ -72,6 +77,10 @@
color: #dc3545;
}
.btn {
margin-top: 5px;
}
.container-xl {
margin-bottom: 40px;
}
@@ -110,7 +119,7 @@
}
&:last-child {
text-align: right;
@media (min-width: 850px) {
@media (min-width: 768px) {
text-align: left;
}
}
@@ -120,26 +129,9 @@
}
}
.effective-fee-container {
.effective-fee-container{
display: block;
@media (min-width: 768px){
display: inline-block;
}
}
.title {
h2 {
line-height: 1;
margin: 0;
padding-bottom: 10px;
}
}
.btn-outline-info {
margin-top: -10px;
@media (min-width: 768px){
display: inline-block;
margin-top: 0px;
}
}
}

View File

@@ -44,7 +44,6 @@ export class TransactionComponent implements OnInit, OnDestroy {
now = new Date().getTime();
timeAvg$: Observable<number>;
liquidUnblinding = new LiquidUnblinding();
outputIndex: number;
constructor(
private route: ActivatedRoute,
@@ -126,9 +125,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.subscription = this.route.paramMap
.pipe(
switchMap((params: ParamMap) => {
const urlMatch = (params.get('id') || '').split(':');
this.txId = urlMatch[0];
this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10);
this.txId = params.get('id') || '';
this.seoService.setTitle(
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
);
@@ -166,7 +163,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.errorUnblinded = error;
return of(tx);
})
);
)
}
return of(tx);
})

View File

@@ -29,18 +29,11 @@
</span>
</ng-template>
<ng-template #hasPrevout>
<ng-template [ngIf]="vin.is_pegin" [ngIfElse]="defaultPrevout">
<a *ngIf="stateService.env.BASE_MODULE === 'liquid'; else localPegInLink" [attr.href]="'https://mempool.space/tx/' + vin.txid" class="red">
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
</a>
<ng-template #localPegInLink>
<a [routerLink]="['/tx/', vin.txid]" class="red">
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
</a>
</ng-template>
</ng-template>
<a *ngIf="vin.is_pegin; else defaultPrevout" [routerLink]="['/tx/', vin.txid]" class="red">
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
</a>
<ng-template #defaultPrevout>
<a [routerLink]="['/tx/' | relativeUrl, vin.txid + ':' + vin.vout]" class="red">
<a [routerLink]="['/tx/' | relativeUrl, vin.txid]" class="red">
<fa-icon [icon]="['fas', 'arrow-alt-circle-right']" [fixedWidth]="true"></fa-icon>
</a>
</ng-template>
@@ -48,7 +41,7 @@
</td>
<td>
<div [ngSwitch]="true">
<ng-container *ngSwitchCase="vin.is_coinbase"><span i18n="transactions-list.coinbase">Coinbase</span><ng-template [ngIf]="network !== 'liquid'">&nbsp;<span i18n="transactions-list.newly-generated-coins">(Newly Generated Coins)</span></ng-template><br /><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii"><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></a></ng-container>
<ng-container *ngSwitchCase="vin.is_coinbase"><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii"><span i18n="transactions-list.coinbase">Coinbase</span><ng-template [ngIf]="network !== 'liquid'">&nbsp;<span i18n="transactions-list.newly-generated-coins">(Newly Generated Coins)</span></ng-template></a><br /><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></ng-container>
<ng-container *ngSwitchCase="vin.is_pegin">
<span i18n="transactions-list.peg-in">Peg-in</span>
</ng-container>
@@ -107,16 +100,10 @@
<td i18n="transactions-list.nsequence">nSequence</td>
<td style="text-align: left;">{{ formatHex(vin.sequence) }}</td>
</tr>
<ng-template [ngIf]="vin.prevout">
<tr>
<td i18n="transactions-list.previous-output-script">Previous output script</td>
<td style="text-align: left;" [innerHTML]="vin.prevout.scriptpubkey_asm | asmStyler"></td>
</tr>
<tr>
<td i18n="transactions-list.previous-output-type">Previous output type</td>
<td style="text-align: left;">{{ vin.prevout.scriptpubkey_type?.toUpperCase() }}</td>
</tr>
</ng-template>
<tr *ngIf="vin.prevout">
<td i18n="transactions-list.previous-output-script">Previous output script</td>
<td style="text-align: left;" [innerHTML]="vin.prevout.scriptpubkey_asm | asmStyler">{{ vin.prevout.scriptpubkey_type ? ('(' + vin.prevout.scriptpubkey_type + ')') : '' }}"</td>
</tr>
</tbody>
</table>
</td>
@@ -134,8 +121,8 @@
<div class="col mobile-bottomcol">
<table class="table table-borderless smaller-text table-sm" id="table-tx-vout">
<tbody>
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] && !outputIndex ? ((tx.vout.length > 12) ? tx.vout.slice(0, 10) : tx.vout.slice(0, 12)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
<tr [ngClass]="assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex ? 'assetBox' : ''">
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] ?((tx.vout.length>12)?tx.vout.slice(0, 10): tx.vout.slice(0, 12)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
<tr [ngClass]="assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded ? 'assetBox' : ''">
<td>
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
@@ -145,21 +132,15 @@
<ng-template [ngIf]="vout.pegout" [ngIfElse]="defaultscriptpubkey_type">
<ng-container i18n="transactions-list.peg-out-to">Peg-out to <ng-container *ngTemplateOutlet="pegOutLink"></ng-container></ng-container>
<ng-template #pegOutLink>
<a *ngIf="stateService.env.BASE_MODULE === 'liquid'; else localPegoutLink" [attr.href]="'https://mempool.space/address/' + vout.pegout.scriptpubkey_address" title="{{ vout.pegout.scriptpubkey_address }}">
<a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.pegout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vout.pegout.scriptpubkey_address | shortenString : 35 }}</span>
</a>
<ng-template #localPegoutLink>
<a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.pegout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-block">{{ vout.pegout.scriptpubkey_address | shortenString : 35 }}</span>
</a>
</ng-template>
</ng-template>
</ng-template>
<ng-template #defaultscriptpubkey_type>
<ng-template [ngIf]="vout.scriptpubkey_type === 'op_return'" [ngIfElse]="otherPubkeyType">
OP_RETURN&nbsp;<a placement="bottom" [ngbTooltip]="vout.scriptpubkey_asm | hex2ascii"><span *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" class="badge badge-secondary scriptmessage">{{ vout.scriptpubkey_asm | hex2ascii }}</span></a>
<a placement="bottom" [ngbTooltip]="vout.scriptpubkey_asm | hex2ascii">OP_RETURN</a>&nbsp;<span *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" class="badge badge-secondary scriptmessage">{{ vout.scriptpubkey_asm | hex2ascii }}</span>
</ng-template>
<ng-template #otherPubkeyType>{{ vout.scriptpubkey_type | scriptpubkeyType }}</ng-template>
</ng-template>
@@ -195,6 +176,10 @@
<td colspan="3" class=" details-container" >
<table class="table table-striped table-borderless details-table mb-3">
<tbody>
<tr *ngIf="vout.scriptpubkey_type">
<td i18n="transactions-list.vout.scriptpubkey-type">Type</td>
<td style="text-align: left;">{{ vout.scriptpubkey_type.toUpperCase() }}</td>
</tr>
<tr>
<td i18n="transactions-list.scriptpubkey.asm|ScriptPubKey (ASM)">ScriptPubKey (ASM)</td>
<td style="text-align: left;" [innerHTML]="vout.scriptpubkey_asm | asmStyler"></td>
@@ -207,16 +192,12 @@
<td>OP_RETURN <span i18n="transactions-list.vout.scriptpubkey-type.data">data</span></td>
<td style="text-align: left;">{{ vout.scriptpubkey_asm | hex2ascii }}</td>
</tr>
<tr *ngIf="vout.scriptpubkey_type">
<td i18n="transactions-list.vout.scriptpubkey-type">Type</td>
<td style="text-align: left;">{{ vout.scriptpubkey_type.toUpperCase() }}</td>
</tr>
</tbody>
</table>
</td>
</tr>
</ng-template>
<tr *ngIf="tx.vout.length > 12 && tx['@voutLimit'] && !outputIndex">
<tr *ngIf="tx.vout.length > 12 && tx['@voutLimit']">
<td colspan="3" class="text-center">
<button class="btn btn-sm btn-primary mt-2" (click)="tx['@voutLimit'] = false;"><span i18n="transactions-list.load-all">Load all</span> ({{ tx.vout.length - 10 }})</button>
</td>

View File

@@ -4,22 +4,22 @@
}
.green, .grey, .red {
font-size: 16px;
top: -2px;
top: -2px;
position: relative;
@media( min-width: 576px){
font-size: 19px;
}
}
.green {
color:#28a745;
color:#28a745;
}
.red {
color:#dc3545;
color:#dc3545;
}
.grey {
color:#6c757d;
color:#6c757d;
}
.mobile-bottomcol {
@@ -43,7 +43,7 @@
}
}
.extra-info {
.extra-info {
display: none;
@media (min-width: 576px) {
display: inline-table;
@@ -75,11 +75,11 @@
}
}
.fiat {
margin-left: 10px;
margin-left: 10px;
}
.tx-page-container {
padding: 10px;
padding: 10px;
margin-bottom: 10px;
margin-top: 10px;
}
@@ -112,10 +112,6 @@
width: 100%;
color: #d43131;
text-align: right;
margin-top: 0px;
margin-bottom: 10px;
}
h2 {
line-height: 1;
}
margin-top: 0px;
margin-bottom: 10px;
}

View File

@@ -22,7 +22,6 @@ export class TransactionsListComponent implements OnInit, OnChanges {
@Input() showConfirmations = false;
@Input() transactionPage = false;
@Input() errorUnblinded = false;
@Input() outputIndex: number;
@Output() loadMore = new EventEmitter();
@@ -31,7 +30,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
assetsMinimal: any;
constructor(
public stateService: StateService,
private stateService: StateService,
private electrsApiService: ElectrsApiService,
private assetsService: AssetsService,
private ref: ChangeDetectorRef,
@@ -52,14 +51,6 @@ export class TransactionsListComponent implements OnInit, OnChanges {
if (!this.transactions || !this.transactions.length) {
return;
}
if (this.outputIndex) {
setTimeout(() => {
const assetBoxElements = document.getElementsByClassName('assetBox');
if (assetBoxElements && assetBoxElements[0]) {
assetBoxElements[0].scrollIntoView();
}
}, 10);
}
const observableObject = {};
this.transactions.forEach((tx, i) => {
tx['@voutLimit'] = true;

View File

@@ -5,6 +5,5 @@
<span *ngIf="segwitGains.potentialP2shGains" class="badge badge-danger mr-1" i18n-ngbTooltip="ngbTooltip about missed out gains" ngbTooltip="This transaction could save {{ segwitGains.potentialBech32Gains * 100 | number : '1.0-0' }}% on fees by upgrading to native SegWit-Bech32 or {{ segwitGains.potentialP2shGains * 100 | number: '1.0-0' }}% by upgrading to SegWit-P2SH" placement="bottom"><del i18n="tx-features.tag.segwit|SegWit">SegWit</del></span>
</ng-template>
</ng-template>
<span *ngIf="isTaproot" class="badge badge-success mr-1" i18n-ngbTooltip="Taproot tooltip" ngbTooltip="This transaction uses Taproot" placement="bottom" i18n="tx-features.tag.taproot">Taproot</span>
<span *ngIf="isRbfTransaction; else rbfDisabled" class="badge badge-success" i18n-ngbTooltip="RBF tooltip" ngbTooltip="This transaction support Replace-By-Fee (RBF) allowing fee bumping" placement="bottom" i18n="tx-features.tag.rbf|RBF">RBF</span>
<ng-template #rbfDisabled><span class="badge badge-danger mr-1" i18n-ngbTooltip="RBF disabled tooltip" ngbTooltip="This transaction does NOT support Replace-By-Fee (RBF) and cannot be fee bumped using this method" placement="bottom"><del i18n="tx-features.tag.rbf|RBF">RBF</del></span></ng-template>

View File

@@ -17,7 +17,6 @@ export class TxFeaturesComponent implements OnChanges {
potentialP2shGains: 0,
};
isRbfTransaction: boolean;
isTaproot: boolean;
constructor() { }
@@ -27,6 +26,5 @@ export class TxFeaturesComponent implements OnChanges {
}
this.segwitGains = calcSegwitFeeGains(this.tx);
this.isRbfTransaction = this.tx.vin.some((v) => v.sequence < 0xfffffffe);
this.isTaproot = this.tx.vin.some((v) => v.prevout && v.prevout.scriptpubkey_type === 'v1_p2tr');
}
}

View File

@@ -47,16 +47,14 @@
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
<hr>
</div>
<ng-container *ngIf="{ value: (mempoolStats$ | async) } as mempoolStats">
<div class="mempool-graph">
<app-mempool-graph
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner">
<app-mempool-graph
[template]="'widget'"
[limitFee]="150"
[limitFilterFee]="1"
[data]="mempoolStats.value?.mempool"
></app-mempool-graph>
</div>
</ng-container>
[data]="mempoolStats.mempool"
></app-mempool-graph>
</div>
</div>
</div>
</div>
@@ -69,10 +67,10 @@
<app-lbtc-pegs-graph [data]="liquidPegsMonth$ | async"></app-lbtc-pegs-graph>
</div>
<ng-template #mempoolGraph>
<div class="mempool-graph" *ngIf="{ value: (mempoolStats$ | async) } as mempoolStats">
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner">
<app-incoming-transactions-graph
[left]="50"
[data]="mempoolStats.value?.weightPerSecond"
[data]="mempoolStats.weightPerSecond"
></app-incoming-transactions-graph>
</div>
</ng-template>
@@ -150,8 +148,6 @@
<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>
|
<a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a>
</div>
</div>
@@ -244,7 +240,7 @@
</div>
<div class="item">
<h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
<div *ngIf="epochData.remainingBlocks < 1870; else recentlyAdjusted" class="card-text" [ngStyle]="{'color': epochData.colorAdjustments}">
<div class="card-text" [ngStyle]="{'color': epochData.colorAdjustments}">
<span *ngIf="epochData.change > 0; else arrowDownDifficulty" >
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
</span>
@@ -254,9 +250,6 @@
{{ epochData.change | absolute | number: '1.2-2' }}
<span class="symbol">%</span>
</div>
<ng-template #recentlyAdjusted>
<div class="card-text">&#8212;</div>
</ng-template>
<div class="symbol">
<span i18n="difficulty-box.previous">Previous</span>:
<span [ngStyle]="{'color': epochData.colorPreviousAdjustments}">
@@ -282,6 +275,13 @@
</div>
</ng-template>
<ng-template #loadingSpinner>
<div class="text-center loadingGraphs">
<div class="spinner-border text-light"></div>
</div>
</ng-template>
<ng-template #loadingDifficulty>
<div class="difficulty-skeleton loading-container">
<div class="item">

View File

@@ -218,7 +218,7 @@
}
.mempool-graph {
height: 255px;
height: 250px;
}
.loadingGraphs{
height: 250px;

View File

@@ -2,13 +2,15 @@ import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@
import { combineLatest, merge, Observable, of, timer } from 'rxjs';
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
import { Block } from '../interfaces/electrs.interface';
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { LiquidPegs, OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service';
import { formatDate } from '@angular/common';
import { WebsocketService } from '../services/websocket.service';
import { SeoService } from '../services/seo.service';
import { StorageService } from '../services/storage.service';
import { EChartsOption } from 'echarts';
interface MempoolBlocksData {
blocks: number;
@@ -142,16 +144,14 @@ export class DashboardComponent implements OnInit {
const newDifficultyHeight = block.height + remainingBlocks;
let change = 0;
if (remainingBlocks < 1870) {
if (blocksInEpoch > 0) {
change = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (change > 300) {
change = 300;
}
if (change < -75) {
change = -75;
}
if (blocksInEpoch > 0) {
change = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (change > 300) {
change = 300;
}
if (change < -75) {
change = -75;
}
const timeAvgDiff = change * 0.1;

View File

@@ -26,8 +26,6 @@ export interface WebsocketResponse {
}
export interface MempoolBlock {
blink?: boolean;
height?: number;
blockSize: number;
blockVSize: number;
nTx: number;

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs } from '../interfaces/node-api.interface';
import { Observable } from 'rxjs';
import { StateService } from './state.service';
@@ -57,14 +57,6 @@ export class ApiService {
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/1y');
}
list2YStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/2y');
}
list3YStatistics$(): Observable<OptimizedMempoolStats[]> {
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/3y');
}
getTransactionTimes$(txIds: string[]): Observable<number[]> {
let params = new HttpParams();
txIds.forEach((txId: string) => {
@@ -112,8 +104,4 @@ export class ApiService {
listLiquidPegsMonth$(): Observable<LiquidPegs[]> {
return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
}
postTransaction$(hexPayload: string): Observable<any> {
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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