Compare commits

..

155 Commits

Author SHA1 Message Date
wiz
139c621a90 Merge remote-tracking branch 'mempooljs/main' into wiz/subtree-merge-mempool.js-repo 2022-01-29 08:02:10 +00:00
wiz
a913a85dea Merge pull request #1199 from mempool/wiz/let-backend-set-its-own-http-cache-time-headers
Let backend set its own HTTP cache time headers for APIs
2022-01-29 04:28:22 +00:00
wiz
f842316636 Let backend set its own HTTP cache time headers for APIs 2022-01-28 13:59:04 +00:00
wiz
05fd433ad7 Merge pull request #1198 from mempool/wiz/enable-nginx-warm-cache-for-mining-pools-data
Enable nginx warm cache for mining pools API data
2022-01-28 13:51:44 +00:00
wiz
55652130a8 Enable nginx warm cache for mining pools API data 2022-01-28 13:33:29 +00:00
wiz
6a8874a9e0 Merge pull request #1193 from mempool/wiz/increase-nginx-timeouts
Increase nginx send_timeout and keepalive_requests
2022-01-28 13:22:21 +00:00
wiz
0afcb53abd Merge pull request #1162 from nymkappa/feature/backend-block-pool-data
Mining dashboard (2/2) - Dashboard PoC
2022-01-28 10:09:17 +00:00
nymkappa
620a7f0718 Fix mining pools wrong default timespan value 2022-01-28 18:49:06 +09:00
nymkappa
9df490373b Fixes post rebase 2022-01-28 15:01:24 +09:00
nymkappa
6ebbc5667d Small improvements on the mining page UX
- INDEXING_BLOCKS_AMOUNT = 0 disable indexing, INDEXING_BLOCKS_AMOUNT = -1 indexes everything
- Show only available timespan in the mining page according to available datas
- Change default INDEXING_BLOCKS_AMOUNT to 1100

Don't use unfiltered mysql user input

Enable http cache header for mining pools (1 min)
2022-01-28 15:01:24 +09:00
nymkappa
d66bc57165 Move block indexing start logic in blocks.ts 2022-01-28 15:01:23 +09:00
nymkappa
73019b485f Update tests - Replace button click blocks -> pools 2022-01-28 15:01:23 +09:00
nymkappa
647f12ffaa Added MEMPOOL.INDEXING_BLOCKS_AMOUNT in the config (default 432 blocks) 2022-01-28 15:01:23 +09:00
nymkappa
a271c39ba8 Wrap block indexing into a try/catch since we don't use async when calling that function 2022-01-28 15:01:22 +09:00
nymkappa
f8f9108ae1 Polish mining page UI
Make sure to wait for all mining pools queries before continuing
2022-01-28 15:01:22 +09:00
nymkappa
40e529ece7 Don't try to identify the mining pool on liquid/bisq 2022-01-28 15:01:21 +09:00
nymkappa
df960ab9ba Import pools file for testnet and signet as well - Added missing express routes 2022-01-28 15:01:21 +09:00
nymkappa
8eaa9b3c7b Add share % in pie chart label 2022-01-28 15:01:21 +09:00
nymkappa
5b32ab6dde DROP -> DROP IF EXISTS 2022-01-28 15:01:20 +09:00
nymkappa
f982f6b4b6 Hide epoch data from the mining page 2022-01-28 15:01:20 +09:00
nymkappa
aa457e316b Drop legacy blocks table during migration - Fix linter issues 2022-01-28 15:01:19 +09:00
nymkappa
b8410f00d9 Fix xxxWindowPreference management 2022-01-28 15:01:19 +09:00
nymkappa
4b9bfd6ca0 Basic block indexing WIP - Default mining pool icon - Only show mining hashrate on 1d scale 2022-01-28 15:01:15 +09:00
nymkappa
b9a047b22d Add difficulty adjustment in mining page + Fix pools table on mobile 2022-01-28 14:52:12 +09:00
nymkappa
091027cc79 When a new blocks is mined, refresh the mining stats 2022-01-28 13:54:59 +09:00
nymkappa
0a267affaf Add pie chart and rewrite the pool ranking component 2022-01-28 13:54:59 +09:00
nymkappa
18a63933fa Increment migration schema version to 3 and re-add pools and blocks table creation queries 2022-01-28 13:53:29 +09:00
nymkappa
bfe9f99c35 Generate mining basic pool ranking (sorted by block found) for a specified timeframe 2022-01-28 13:52:35 +09:00
nymkappa
37031ec913 Refactor blocks.ts and index 10k block headers at launch 2022-01-28 13:51:14 +09:00
nymkappa
031f69a403 Add backend README - Backend watchers setup 2022-01-28 13:50:58 +09:00
nymkappa
1a22923cd8 Migrate pools.json to the database in one command - Updated latest pools.json file from Blockchain-Known-Pools master 2022-01-28 13:50:58 +09:00
wiz
4212a649f1 Merge pull request #1194 from AaronDewes/patch-1
Change Citadel link to core
2022-01-27 09:03:12 +00:00
Aaron Dewes
8a4a003620 Change Citadel link to core 2022-01-27 08:11:18 +01:00
wiz
493dbb1b1a Increase nginx send_timeout and keepalive_requests
Fixes #425
2022-01-27 05:56:38 +00:00
wiz
356dda96c8 Merge pull request #1188 from mempool/wiz/add-cla
Add new Contributor License Agreement policy
2022-01-25 10:22:21 +00:00
softsimon
f2a9933d21 Accept the CLA for @softsimon 2022-01-25 13:41:28 +04:00
wiz
bd033541b7 Update copyright years in legal notices 2022-01-25 09:36:02 +00:00
wiz
707ae7be01 Accept the CLA for @wiz 2022-01-25 09:35:48 +00:00
wiz
000dfc4d9e Add new Contributor License Agreement policy 2022-01-25 09:30:11 +00:00
wiz
cba46a82aa Merge pull request #1173 from mempool/simon/liquid-asset-precision-fix
Fixing Liquid asset precision
2022-01-25 08:15:20 +00:00
wiz
1263d05ac8 Merge pull request #1187 from nymkappa/bugfix/mysql-transactions
Remove useless autocommit=0 in db migration script
2022-01-25 07:51:31 +00:00
nymkappa
703b4cc92a Remove useless autocommit=0 in db migration script 2022-01-25 16:45:52 +09:00
wiz
6ad0344ea5 Merge pull request #1163 from nymkappa/feature/pools-migration
Mining dashboard (1/2) - Import mining pools into the database - Increment db schema to 3
2022-01-25 06:27:10 +00:00
softsimon
eb32b13acb Merge pull request #1182 from nymkappa/bugfix/statistics-db-error-handling
Wrap statistics db ops with try/catch
2022-01-24 17:05:48 +04:00
softsimon
f1400909a8 Merge pull request #1183 from nymkappa/feature/cleanup-master-page
Cleanup master-page template
2022-01-24 17:03:16 +04:00
softsimon
c91b6b473a Merge pull request #1177 from knorrium/fix_broken_bisq_tx_link
Fix broken link on the Bisq transaction page
2022-01-24 13:23:11 +04:00
nymkappa
68320dc117 Cleanup master-page template 2022-01-24 18:22:15 +09:00
nymkappa
a805c86697 Wrap statistics db ops with try/catch 2022-01-24 16:22:38 +09:00
nymkappa
1322298a06 Make sure to wait for all mining pools queries before continuing 2022-01-24 14:34:03 +09:00
softsimon
230f563235 Merge pull request #1178 from knorrium/update_e2e_tests
Update e2e tests: new scenarios for Liquid, Bisq and refactor button selectors
2022-01-23 14:25:11 +04:00
softsimon
d1ed3c4b93 Merge pull request #1179 from mempool/simon/frontend-npm-audit-fix
Npm audit fix
2022-01-23 12:58:39 +04:00
softsimon
c1f90e0c26 Npm audit fix 2022-01-23 12:58:16 +04:00
Felipe Knorr Kuhn
0ff6cd19c3 Update tests: use ids for nav bar items and new scenarios for bisq and liquid 2022-01-22 14:23:09 -08:00
Felipe Knorr Kuhn
a5ca0cda14 Add ids to nav bar items, liquid and bisq components 2022-01-22 14:21:46 -08:00
Felipe Knorr Kuhn
0560496154 Fix broken link on the Bisq transaction page 2022-01-21 23:12:18 -08:00
softsimon
e2dfdc0064 Merge pull request #1175 from knorrium/update_to_cypress_931
Update Cypress to v9.3.1
2022-01-21 15:37:54 +04:00
softsimon
a09910522b Merge pull request #1176 from knorrium/update_liquid_tests
Update Liquid tests
2022-01-21 15:37:20 +04:00
wiz
145bdca3af Merge pull request #1172 from mempool/simon/liquid-asset-overflow
Asset name overflow fix
2022-01-21 08:55:50 +00:00
Felipe Knorr Kuhn
af38ef8ee7 Merge branch 'master' into update_liquid_tests 2022-01-20 21:54:23 -08:00
Felipe Knorr Kuhn
7bb95ff177 Update Cypress GHA spec list 2022-01-20 21:43:13 -08:00
Felipe Knorr Kuhn
c885187971 Add an amount class vins and vouts to improve testing 2022-01-20 21:31:14 -08:00
Felipe Knorr Kuhn
6637477ac9 Update Liquid tests 2022-01-20 21:30:22 -08:00
Felipe Knorr Kuhn
cf5cce23f3 Add Liquid Testnet tests 2022-01-20 21:30:03 -08:00
Felipe Knorr Kuhn
9f2d0c5172 Update config script defaults 2022-01-20 21:29:45 -08:00
Felipe Knorr Kuhn
80e4141612 Update Cypress to v9.3.1 2022-01-20 21:27:52 -08:00
softsimon
a8c04624f0 Fixing liqud asset precision
fixes #1166
2022-01-21 01:32:19 +04:00
softsimon
36b4812e93 Asset name overflow fix 2022-01-21 00:16:18 +04:00
softsimon
347c386815 Merge pull request #1171 from knorrium/fix_liquid_proxy
Fix Liquid proxy settings
2022-01-20 23:42:57 +04:00
softsimon
b2fac709f9 Merge pull request #1170 from mempool/simon/liquid-loading-fixes
Liquid asset loading fixes
2022-01-20 23:30:44 +04:00
Felipe Knorr Kuhn
35e69f2e3d Fix Liquid proxy settings 2022-01-20 11:27:50 -08:00
softsimon
88a9e22abe Liquid asset loading fixes 2022-01-20 23:00:43 +04:00
wiz
2b1367afd8 Merge pull request #1167 from mempool/simon/asset-icons
Display Liquid asset icons
2022-01-20 16:11:12 +00:00
softsimon
a2b167fc07 Display Liquid asset icons 2022-01-20 19:51:02 +04:00
nymkappa
87175869dd Fix typescript miss use 2022-01-20 23:31:32 +09:00
nymkappa
a1a2e9363f Make sure to release all db connections 2022-01-20 23:07:20 +09:00
nymkappa
19a564062b Add pools.json file in default config.ts - Handle file exception - Only import pools for MAINNET 2022-01-20 22:59:10 +09:00
nymkappa
8d1cc40459 Fix add 'Unknown' pool logic 2022-01-20 16:56:25 +09:00
nymkappa
1210643e8e Fix linter issues and typo 2022-01-20 16:34:14 +09:00
nymkappa
979c52d3c4 Add pools.json to EXTERNAL_ASSETS - Now supports updating the table 2022-01-20 13:53:08 +09:00
nymkappa
2848f56c2b Import mining pools into the database - Increment db schema to 3 2022-01-19 18:50:52 +09:00
softsimon
ab6a0eae09 Merge pull request #1158 from antonilol/coinbase
dont use hardcoded genesis coinbase and block hash
2022-01-19 13:33:45 +04:00
softsimon
bc925a409f Merge pull request #1152 from nymkappa/feature/split-difficulty-component
Move difficulty adjustment code to separate module
2022-01-19 13:19:17 +04:00
Antoni Spaanderman
fac40b1515 rethrow the error if it wasnt the genesis coinbase 2022-01-19 08:27:51 +01:00
Antoni Spaanderman
d4719245f5 dont use hardcoded genesis coinbase and block hash
fixes #1128
2022-01-18 21:55:09 +01:00
wiz
3a67bc6425 Merge pull request #1154 from nymkappa/bugfix/extreme-filter-only-mainnet
Only apply vbytes/sec cap on Bitcoin mainnet - Fix linter issues
2022-01-17 21:06:02 +09:00
nymkappa
08a9cc30ba Only apply vbytes/sec cap on Bitcoin mainnet - Fix linter issues 2022-01-17 20:19:20 +09:00
wiz
9641a00bb4 Merge pull request #1151 from nymkappa/feature/save-zeroed-statistics
Insert zeroed statistics in the database if the mempool is empty
2022-01-17 19:08:35 +09:00
nymkappa
fcca911377 Move difficulty adjustment code to separate module 2022-01-17 13:33:07 +09:00
wiz
274ca33664 Merge pull request #1150 from mempool/release/v2.3.0 2022-01-16 18:56:08 +09:00
nymkappa
a570812d70 Insert zeroed statistics in the database if the mempool is empty 2022-01-16 16:20:45 +09:00
wiz
dbdc87eeae Bump version number to v2.4.0-dev 2022-01-16 15:41:23 +09:00
softsimon
3f0ab7307a Bumping version to 2.3.0 2022-01-10 15:53:10 +04:00
softsimon
7e8d5547fd Bumping to 2.3.0-dev1 2021-12-22 19:31:25 +04:00
softsimon
029cd8ad48 Bumping package version to v2.2.6 2021-12-20 23:36:19 +04:00
softsimon
f2a8ac7087 Updating endpoint addresses for Liquid and Bisq backend 2021-12-20 23:34:52 +04:00
softsimon
721ed60ba5 Fixing type for image response 2021-12-20 23:34:36 +04:00
softsimon
4893ff1d81 Adding support for Asset Icon API endpoints 2021-12-20 23:25:44 +04:00
softsimon
d238b1a779 Correcting "blocks" typo 2021-12-20 23:14:11 +04:00
softsimon
a9b2f31ae5 Merge pull request #32 from mikeriss/patch-2
Typo fixed
2021-12-20 23:13:32 +04:00
Dmitry Martynenko
ce56c1b03b websocket testnet url fix 2021-12-20 23:08:13 +04:00
softsimon
b5a8a5dc24 Merge pull request #35 from mempool/simon/version-bump-2.2.4
Bumping dependency versions and package version to 2.2.4
2021-12-20 22:26:57 +04:00
softsimon
06f23c5f37 Bumping dependency versions and package version to 2.2.4 2021-12-20 22:25:12 +04:00
softsimon
870bae5180 Merge pull request #30 from Draichi/patch-1
Update README-bitcoin.md
2021-12-08 21:36:58 +04:00
mikeriss
a614ad4119 Typo fixed 2021-11-02 17:15:01 +01:00
mikeriss
3a42e20cf1 Typo (#31)
changed res.blocks to res.block
2021-10-25 09:21:01 -03:00
Lucas
905ef5ed19 Update README-bitcoin.md
Fix 404 on page links
2021-09-06 13:36:58 -03:00
Miguel Medeiros
9fe77e49a2 Change liquid address doc example. 2021-08-10 13:09:38 -03:00
Miguel Medeiros
62902ba8c7 Add liquid.js documentation. 2021-08-10 13:03:23 -03:00
Miguel Medeiros
1873eb41c6 Change version to 2.2.1 for bisq / liquid modules. 2021-08-10 12:39:11 -03:00
Miguel Medeiros
efd0436851 Add bisq.js readme. 2021-08-10 12:38:33 -03:00
Miguel Medeiros
edab1ad3d5 Fix post tx documentation. 2021-08-10 01:44:50 -03:00
Miguel Medeiros
4efc927303 Add bisqJS and liquidJS npm modules. 2021-08-10 01:30:13 -03:00
Miguel Medeiros
acc0c80953 2.2.4 2021-08-04 08:11:27 -03:00
Miguel Medeiros
334ed2b173 Fix difficulty adjustment endpoint. 2021-08-04 08:09:29 -03:00
Miguel Medeiros
24f0ab8f84 2.2.3 2021-07-23 18:08:43 -03:00
Miguel Medeiros
c1f41d6e1c Add difficulty adjustment documentation link. 2021-07-23 18:02:14 -03:00
Miguel Medeiros
aed4bc5fc9 Add difficulty adjustment examples. 2021-07-23 17:58:54 -03:00
Miguel Medeiros
caf8d956b5 Add difficulty adjustment endpoints methods. 2021-07-23 17:37:28 -03:00
Miguel Medeiros
1c32b05abe Add localhost support. 2021-07-23 17:36:40 -03:00
Miguel Medeiros
7dec92a1bf Change documentation link to official mempool.space. 2021-07-22 02:22:03 -03:00
Miguel Medeiros
4cd7665e8c Add blockHeader instructions and examples. 2021-07-22 02:18:15 -03:00
Miguel Medeiros
47fa100786 Add getBlockHeader method. 2021-07-22 02:14:11 -03:00
Miguel Medeiros
f244ad191e Change command build. 2021-07-22 00:55:29 -03:00
Miguel Medeiros
92bf87821a 2.2.2 2021-05-31 21:03:20 -03:00
Miguel Medeiros
920622137b Fix readme instructions. 2021-05-31 20:36:00 -03:00
Miguel Medeiros
b3606e46c1 Fix getAddress endpoint url. 2021-05-31 20:02:16 -03:00
Miguel Medeiros
112e54ae57 2.2.1 (#22) 2021-05-20 12:25:11 -03:00
Miguel Medeiros
e3068c2d8d Bugfix Websocket hostname. (#21)
* Add yarn-error.log to gitignore.

* Add ts-node to devDependencies.
Add script to only build tsc.
Add websocket to keywords.

* Update yarn.lock libs.

* FIX websocket endpoint hostname.
FIX corrent import for all examples.
FIX websocket instrucions on readme.
2021-05-20 12:03:40 -03:00
Miguel Medeiros
0aa3757217 FIX rename package. 2021-04-17 00:34:47 -03:00
Miguel Medeiros
f26adbd982 FIX rename package name. (#19) (#20) 2021-04-17 00:20:32 -03:00
Miguel Medeiros
b63b5d7d92 2.2.0 2021-04-16 22:51:40 -03:00
Miguel Medeiros
ad41063fdb Merge branch 'main' of https://github.com/mempool/mempool-js 2021-04-15 15:54:04 -03:00
Miguel Medeiros
9c483af487 v2.2.0 (#18)
* FIX: getBlocks optional params

* v2.3.0 - new minor version for mempool-js
- Add support for Bisq API
- Add support for Liquid API
- Change the main object to export network objects.
- Change README.md instructions.

* 2.3.0

* FIX wrong npm link. (#15)

* FIX version name v2.2.0 (#17)

Co-authored-by: softsimon <softsimon@users.noreply.github.com>
2021-04-15 15:33:12 -03:00
Miguel Medeiros
5cea5595f9 2.3.0 2021-04-14 17:30:38 -03:00
Miguel Medeiros
c80f82a0b1 v2.3.0 (#12)
* FIX: getBlocks optional params

* v2.3.0 - new minor version for mempool-js
- Add support for Bisq API
- Add support for Liquid API
- Change the main object to export network objects.
- Change README.md instructions.

Co-authored-by: softsimon <softsimon@users.noreply.github.com>
2021-04-14 17:27:28 -03:00
softsimon
70d31f2062 Merge pull request #9 from mempool/feature/add-cnd-link
Add CDN provider to the mempool.js.
2021-04-10 19:38:07 +04:00
Miguel Medeiros
2dd50add4a - Add mempool.js link to all examples.
- Ignore files in dist folder, adding .gitkeep instead.
- Update readme text with CND instructions.
- Update readme badges.
- Update readme typo.
- Update all the html examples with new CDN link.
2021-04-10 12:30:24 -03:00
Miguel Medeiros
760bf97c1b 2.2.1 2021-04-10 11:53:07 -03:00
softsimon
f7ab448cd1 Merge pull request #8 from mempool/bugfix/build-script-standalone-tinyify
FIX: change build script to enable standalone
2021-04-10 18:45:36 +04:00
Miguel Medeiros
5003538319 FIX: change build script to enable standalone 2021-04-10 11:31:16 -03:00
Miguel Medeiros
f39361263a v2.2.0 - new major version for mempool-js (#2)
* - Refactoring code.
- Refactoring folder structure.
- Adding apiEndpoint and websocketEndpoint to Mempool config.
- Adding brownserify feature.
- Adding MIT LICENSE

* - Changing package.json information.
- Reorganizing README.md information.
- Default export for CommonJs and ES6 Modules.
- Changing default variable to mempoolJS.
- Organizing the API and WS providers.
- Splitting websocket connection types: client and server.

* Change version to 2.2.0.
Reorder keywords in alphabetical order.
2021-04-08 10:15:30 -03:00
MiguelMedeiros\Miguel Medeiros
3bfae54069 1.1.2 2021-02-10 11:26:46 -03:00
MiguelMedeiros\Miguel Medeiros
6efc2aefe4 Change URL website. 2021-02-10 11:26:34 -03:00
MiguelMedeiros\Miguel Medeiros
e837bf7be3 1.1.1 2021-02-10 11:20:13 -03:00
MiguelMedeiros\Miguel Medeiros
8fe9a67352 Changing readme badges. 2021-02-10 11:20:01 -03:00
MiguelMedeiros\Miguel Medeiros
68a11bfae5 1.1.0 2021-02-09 16:23:52 -03:00
MiguelMedeiros\Miguel Medeiros
90be85f6f0 Adding websocket. 2021-02-09 16:22:48 -03:00
MiguelMedeiros\Miguel Medeiros
b39fb0bd60 1.0.2 2021-02-08 17:13:27 -03:00
MiguelMedeiros\Miguel Medeiros
9c5c1b8ff0 Changing badges links. 2021-02-08 17:13:15 -03:00
MiguelMedeiros\Miguel Medeiros
eacf5a584d 1.0.1 2021-02-08 17:11:10 -03:00
MiguelMedeiros\Miguel Medeiros
6946f22aea Changing Donations addresses. 2021-02-08 17:10:19 -03:00
MiguelMedeiros\Miguel Medeiros
caed38420b Changing project name. 2021-02-08 17:03:18 -03:00
MiguelMedeiros\Miguel Medeiros
67ad99006f Changing text Instalation. 2021-02-08 17:00:32 -03:00
MiguelMedeiros\Miguel Medeiros
9a97b411c9 Adding links to README.md. 2021-02-08 16:59:48 -03:00
MiguelMedeiros\Miguel Medeiros
ff53139f78 Init 2021-02-08 16:54:37 -03:00
191 changed files with 24321 additions and 1528 deletions

View File

@@ -31,6 +31,10 @@ jobs:
wait-on-timeout: 120
record: true
parallel: true
spec: |
cypress/integration/mainnet/*.spec.ts
cypress/integration/signet/*.spec.ts
cypress/integration/testnet/*.spec.ts
group: Tests on ${{ matrix.browser }} (Mempool)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
@@ -50,7 +54,9 @@ jobs:
wait-on-timeout: 120
record: true
parallel: true
spec: cypress/integration/liquid/liquid.spec.ts
spec: |
cypress/integration/liquid/liquid.spec.ts
cypress/integration/liquidtestnet/liquidtestnet.spec.ts
group: Tests on ${{ matrix.browser }} (Liquid)
browser: ${{ matrix.browser }}
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'

49
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,49 @@
# Contributing to The Mempool Open Source Project
Thank you for contributing to The Mempool Open Source Project managed by Mempool Space K.K. (“Mempool”).
In order to clarify the intellectual property license granted with Contributions from any person or entity, Mempool must have a statement on file from each Contributor indicating their agreement to the Contributor License Agreement (“Agreement”). This license is for your protection as a Contributor as well as the protection of Mempool and its other contributors and users; it does not change your rights to use your own Contributions for any other purpose.
When submitting a pull request for the first time, please create a file with a name like `/contributors/{github_username}.txt`, and in the content of that file indicate your agreement to the Contributor License Agreement terms below. An example of what that file should contain can be seen in wiz's agreement file. (This method of CLA "signing" is borrowed from Medium's open source project.)
# Contributor License Agreement
Last Updated: January 25, 2022
By accepting this Agreement, You agree to the following terms and conditions for Your present and future Contributions submitted to Mempool. Except for the license granted herein to Mempool and recipients of software distributed by Mempool, You reserve all right, title, and interest in and to Your Contributions.
### 1. Definitions
“You” (or “Your”) shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Mempool. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
“Contribution” shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Mempool for inclusion in, or documentation of, any of the products owned or managed by Mempool (“Work”). For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to Mempool or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Mempool for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as “Not a Contribution.”
### 2. Grant of Copyright License
Subject to the terms and conditions of this Agreement, You hereby grant to Mempool and to recipients of software distributed by Mempool a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
### 3. Grant of Patent License
Subject to the terms and conditions of this Agreement, You hereby grant to Mempool and to recipients of software distributed by Mempool a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
### 4. Authority
You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to Mempool, or that your employer has executed a separate Corporate Contributor License Agreement with Mempool.
### 5. Originality
You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware, and which are associated with any part of Your Contributions.
### 6. Support
You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
### 7. Third Party Contributions
Should You wish to submit work that is not Your original creation, You may submit it to Mempool separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as “Submitted on behalf of a third-party: [named here]”.
### 8. Notifications
You agree to notify Mempool of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
EOF

View File

@@ -1,5 +1,5 @@
The Mempool Open Source Project
Copyright (c) 2019-2021 The Mempool Open Source Project Developers
Copyright (c) 2019-2022 The Mempool Open Source Project Developers
This program is free software; you can redistribute it and/or modify it under
the terms of (at your option) either:

22
backend/README.md Normal file
View File

@@ -0,0 +1,22 @@
# Setup backend watchers
The backend is static. Typescript scripts are compiled into the `dist` folder and served through a node web server.
You can avoid the manual shutdown/recompile/restart command line cycle by using a watcher.
Make sure you are in the `backend` directory `cd backend`.
1. Install nodemon and ts-node
```
sudo npm install -g ts-node nodemon
```
2. Run the watcher
> Note: You can find your npm global binary folder using `npm -g bin`, where nodemon will be installed.
```
nodemon src/index.ts --ignore cache/ --ignore pools.json
```

View File

@@ -12,9 +12,12 @@
"BLOCK_WEIGHT_UNITS": 4000000,
"INITIAL_BLOCKS_AMOUNT": 8,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"INDEXING_BLOCKS_AMOUNT": 1100,
"PRICE_FEED_UPDATE_INTERVAL": 3600,
"USE_SECOND_NODE_FOR_MINFEE": false,
"EXTERNAL_ASSETS": []
"EXTERNAL_ASSETS": [
"https://mempool.space/resources/pools.json"
]
},
"CORE_RPC": {
"HOST": "127.0.0.1",

View File

@@ -1,12 +1,12 @@
{
"name": "mempool-backend",
"version": "2.3.1",
"version": "2.4.0-dev",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mempool-backend",
"version": "2.3.1",
"version": "2.4.0-dev",
"license": "GNU Affero General Public License v3.0",
"dependencies": {
"@mempool/bitcoin": "^3.0.3",

View File

@@ -1,6 +1,6 @@
{
"name": "mempool-backend",
"version": "2.3.1",
"version": "2.4.0-dev",
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
"license": "GNU Affero General Public License v3.0",
"homepage": "https://mempool.space",

View File

@@ -21,11 +21,6 @@ class BitcoinApi implements AbstractBitcoinApi {
return this.$addPrevouts(txInMempool);
}
// Special case to fetch the Coinbase transaction
if (txId === '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b') {
return this.$returnCoinbaseTransaction();
}
return this.bitcoindClient.getRawTransaction(txId, true)
.then((transaction: IBitcoinApi.Transaction) => {
if (skipConversion) {
@@ -35,6 +30,12 @@ class BitcoinApi implements AbstractBitcoinApi {
return transaction;
}
return this.$convertTransaction(transaction, addPrevout);
})
.catch((e: Error) => {
if (e.message.startsWith('The genesis block coinbase')) {
return this.$returnCoinbaseTransaction();
}
throw e;
});
}
@@ -114,6 +115,11 @@ class BitcoinApi implements AbstractBitcoinApi {
return outSpends;
}
$getEstimatedHashrate(blockHeight: number): Promise<number> {
// 120 is the default block span in Core
return this.bitcoindClient.getNetworkHashPs(120, blockHeight);
}
protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean): Promise<IEsploraApi.Transaction> {
let esploraTransaction: IEsploraApi.Transaction = {
txid: transaction.txid,
@@ -238,12 +244,14 @@ class BitcoinApi implements AbstractBitcoinApi {
}
protected $returnCoinbaseTransaction(): Promise<IEsploraApi.Transaction> {
return this.bitcoindClient.getBlock('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 2)
.then((block: IBitcoinApi.Block) => {
return this.$convertTransaction(Object.assign(block.tx[0], {
confirmations: blocks.getCurrentBlockHeight() + 1,
blocktime: 1231006505 }), false);
});
return this.bitcoindClient.getBlockHash(0).then((hash: string) =>
this.bitcoindClient.getBlock(hash, 2)
.then((block: IBitcoinApi.Block) => {
return this.$convertTransaction(Object.assign(block.tx[0], {
confirmations: blocks.getCurrentBlockHeight() + 1,
blocktime: block.time }), false);
})
);
}
private $getMempoolEntry(txid: string): Promise<IBitcoinApi.MempoolEntry> {

View File

@@ -2,11 +2,14 @@ import config from '../config';
import bitcoinApi from './bitcoin/bitcoin-api-factory';
import logger from '../logger';
import memPool from './mempool';
import { BlockExtended, TransactionExtended } from '../mempool.interfaces';
import { BlockExtended, PoolTag, TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
import { Common } from './common';
import diskCache from './disk-cache';
import transactionUtils from './transaction-utils';
import bitcoinClient from './bitcoin/bitcoin-client';
import { IEsploraApi } from './bitcoin/esplora-api.interface';
import poolsRepository from '../repositories/PoolsRepository';
import blocksRepository from '../repositories/BlocksRepository';
class Blocks {
private blocks: BlockExtended[] = [];
@@ -15,6 +18,7 @@ class Blocks {
private lastDifficultyAdjustmentTime = 0;
private previousDifficultyRetarget = 0;
private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
private blockIndexingStarted = false;
constructor() { }
@@ -30,6 +34,186 @@ class Blocks {
this.newBlockCallbacks.push(fn);
}
/**
* Return the list of transaction for a block
* @param blockHash
* @param blockHeight
* @param onlyCoinbase - Set to true if you only need the coinbase transaction
* @returns Promise<TransactionExtended[]>
*/
private async $getTransactionsExtended(blockHash: string, blockHeight: number, onlyCoinbase: boolean): Promise<TransactionExtended[]> {
const transactions: TransactionExtended[] = [];
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
const mempool = memPool.getMempool();
let transactionsFound = 0;
let transactionsFetched = 0;
for (let i = 0; i < txIds.length; i++) {
if (mempool[txIds[i]]) {
// We update blocks before the mempool (index.ts), therefore we can
// optimize here by directly fetching txs in the "outdated" mempool
transactions.push(mempool[txIds[i]]);
transactionsFound++;
} else if (config.MEMPOOL.BACKEND === 'esplora' || memPool.isInSync() || i === 0) {
// Otherwise we fetch the tx data through backend services (esplora, electrum, core rpc...)
if (i % (Math.round((txIds.length) / 10)) === 0 || i + 1 === txIds.length) { // Avoid log spam
logger.debug(`Indexing tx ${i + 1} of ${txIds.length} in block #${blockHeight}`);
}
try {
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
transactions.push(tx);
transactionsFetched++;
} catch (e) {
logger.debug('Error fetching block tx: ' + (e instanceof Error ? e.message : e));
if (i === 0) {
throw new Error('Failed to fetch Coinbase transaction: ' + txIds[i]);
}
}
}
if (onlyCoinbase === true) {
break; // Fetch the first transaction and exit
}
}
transactions.forEach((tx) => {
if (!tx.cpfpChecked) {
Common.setRelativesAndGetCpfpInfo(tx, mempool); // Child Pay For Parent
}
});
logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${transactionsFetched} fetched through backend service.`);
return transactions;
}
/**
* Return a block with additional data (reward, coinbase, fees...)
* @param block
* @param transactions
* @returns BlockExtended
*/
private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended {
const blockExtended: BlockExtended = Object.assign({}, block);
blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
const transactionsTmp = [...transactions];
transactionsTmp.shift();
transactionsTmp.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
blockExtended.medianFee = transactionsTmp.length > 0 ? Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0;
blockExtended.feeRange = transactionsTmp.length > 0 ? Common.getFeesInRange(transactionsTmp, 8) : [0, 0];
return blockExtended;
}
/**
* Try to find which miner found the block
* @param txMinerInfo
* @returns
*/
private async $findBlockMiner(txMinerInfo: TransactionMinerInfo | undefined): Promise<PoolTag> {
if (txMinerInfo === undefined || txMinerInfo.vout.length < 1) {
return await poolsRepository.$getUnknownPool();
}
const asciiScriptSig = transactionUtils.hex2ascii(txMinerInfo.vin[0].scriptsig);
const address = txMinerInfo.vout[0].scriptpubkey_address;
const pools: PoolTag[] = await poolsRepository.$getPools();
for (let i = 0; i < pools.length; ++i) {
if (address !== undefined) {
const addresses: string[] = JSON.parse(pools[i].addresses);
if (addresses.indexOf(address) !== -1) {
return pools[i];
}
}
const regexes: string[] = JSON.parse(pools[i].regexes);
for (let y = 0; y < regexes.length; ++y) {
const match = asciiScriptSig.match(regexes[y]);
if (match !== null) {
return pools[i];
}
}
}
return await poolsRepository.$getUnknownPool();
}
/**
* Index all blocks metadata for the mining dashboard
*/
public async $generateBlockDatabase() {
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false || // Bitcoin only
config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === 0 || // Indexing must be enabled
!memPool.isInSync() || // We sync the mempool first
this.blockIndexingStarted === true // Indexing must not already be in progress
) {
return;
}
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
if (blockchainInfo.blocks !== blockchainInfo.headers) {
return;
}
this.blockIndexingStarted = true;
try {
let currentBlockHeight = blockchainInfo.blocks;
let indexingBlockAmount = config.MEMPOOL.INDEXING_BLOCKS_AMOUNT;
if (indexingBlockAmount <= -1) {
indexingBlockAmount = currentBlockHeight + 1;
}
const lastBlockToIndex = Math.max(0, currentBlockHeight - indexingBlockAmount + 1);
logger.info(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`);
const chunkSize = 10000;
while (currentBlockHeight >= lastBlockToIndex) {
const endBlock = Math.max(0, lastBlockToIndex, currentBlockHeight - chunkSize + 1);
const missingBlockHeights: number[] = await blocksRepository.$getMissingBlocksBetweenHeights(
currentBlockHeight, endBlock);
if (missingBlockHeights.length <= 0) {
logger.debug(`No missing blocks between #${currentBlockHeight} to #${endBlock}`);
currentBlockHeight -= chunkSize;
continue;
}
logger.debug(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`);
for (const blockHeight of missingBlockHeights) {
if (blockHeight < lastBlockToIndex) {
break;
}
try {
logger.debug(`Indexing block #${blockHeight}`);
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
const block = await bitcoinApi.$getBlock(blockHash);
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
const blockExtended = this.getBlockExtended(block, transactions);
const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
} catch (e) {
logger.err(`Something went wrong while indexing blocks.` + e);
}
}
currentBlockHeight -= chunkSize;
}
logger.info('Block indexing completed');
} catch (e) {
logger.err('An error occured in $generateBlockDatabase(). Skipping block indexing. ' + e);
console.log(e);
}
}
public async $updateBlocks() {
const blockHeightTip = await bitcoinApi.$getBlockHeightTip();
@@ -70,49 +254,18 @@ class Blocks {
logger.debug(`New block found (#${this.currentBlockHeight})!`);
}
const transactions: TransactionExtended[] = [];
const blockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight);
const block = await bitcoinApi.$getBlock(blockHash);
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
const transactions = await this.$getTransactionsExtended(blockHash, block.height, false);
const blockExtended: BlockExtended = this.getBlockExtended(block, transactions);
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
const mempool = memPool.getMempool();
let transactionsFound = 0;
for (let i = 0; i < txIds.length; i++) {
if (mempool[txIds[i]]) {
transactions.push(mempool[txIds[i]]);
transactionsFound++;
} else if (config.MEMPOOL.BACKEND === 'esplora' || memPool.isInSync() || i === 0) {
logger.debug(`Fetching block tx ${i} of ${txIds.length}`);
try {
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
transactions.push(tx);
} catch (e) {
logger.debug('Error fetching block tx: ' + (e instanceof Error ? e.message : e));
if (i === 0) {
throw new Error('Failed to fetch Coinbase transaction: ' + txIds[i]);
}
}
}
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === true) {
const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
}
transactions.forEach((tx) => {
if (!tx.cpfpChecked) {
Common.setRelativesAndGetCpfpInfo(tx, mempool);
}
});
logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${txIds.length - transactionsFound} not found.`);
const blockExtended: BlockExtended = Object.assign({}, block);
blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
transactions.shift();
transactions.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
blockExtended.medianFee = transactions.length > 0 ? Common.median(transactions.map((tx) => tx.effectiveFeePerVsize)) : 0;
blockExtended.feeRange = transactions.length > 0 ? Common.getFeesInRange(transactions, 8) : [0, 0];
if (block.height % 2016 === 0) {
this.previousDifficultyRetarget = (block.difficulty - this.currentDifficulty) / this.currentDifficulty * 100;
this.lastDifficultyAdjustmentTime = block.timestamp;
@@ -130,6 +283,8 @@ class Blocks {
if (memPool.isInSync()) {
diskCache.$saveCacheToDisk();
}
return;
}
}

View File

@@ -3,10 +3,10 @@ import config from '../config';
import { DB } from '../database';
import logger from '../logger';
const sleep = (ms: number) => new Promise( res => setTimeout(res, ms));
const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
class DatabaseMigration {
private static currentVersion = 2;
private static currentVersion = 4;
private queryTimeout = 120000;
private statisticsAddedIndexed = false;
@@ -83,6 +83,13 @@ class DatabaseMigration {
if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) {
await this.$executeQuery(connection, `CREATE INDEX added ON statistics (added);`);
}
if (databaseSchemaVersion < 3) {
await this.$executeQuery(connection, this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools'));
}
if (databaseSchemaVersion < 4) {
await this.$executeQuery(connection, 'DROP table IF EXISTS blocks;');
await this.$executeQuery(connection, this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks'));
}
connection.release();
} catch (e) {
connection.release();
@@ -334,6 +341,37 @@ class DatabaseMigration {
final_tx int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
}
private getCreatePoolsTableQuery(): string {
return `CREATE TABLE IF NOT EXISTS pools (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(50) NOT NULL,
link varchar(255) NOT NULL,
addresses text NOT NULL,
regexes text NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`;
}
private getCreateBlocksTableQuery(): string {
return `CREATE TABLE IF NOT EXISTS blocks (
height int(11) unsigned NOT NULL,
hash varchar(65) NOT NULL,
blockTimestamp timestamp NOT NULL,
size int(11) unsigned NOT NULL,
weight int(11) unsigned NOT NULL,
tx_count int(11) unsigned NOT NULL,
coinbase_raw text,
difficulty bigint(20) unsigned NOT NULL,
pool_id int(11) DEFAULT -1,
fees double unsigned NOT NULL,
fee_span json NOT NULL,
median_fee double unsigned NOT NULL,
PRIMARY KEY (height),
INDEX (pool_id),
FOREIGN KEY (pool_id) REFERENCES pools (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
}
}
export default new DatabaseMigration();

69
backend/src/api/mining.ts Normal file
View File

@@ -0,0 +1,69 @@
import { PoolInfo, PoolStats } from '../mempool.interfaces';
import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository';
import PoolsRepository from '../repositories/PoolsRepository';
import bitcoinClient from './bitcoin/bitcoin-client';
class Mining {
constructor() {
}
/**
* Generate high level overview of the pool ranks and general stats
*/
public async $getPoolsStats(interval: string | null) : Promise<object> {
let sqlInterval: string | null = null;
switch (interval) {
case '24h': sqlInterval = '1 DAY'; break;
case '3d': sqlInterval = '3 DAY'; break;
case '1w': sqlInterval = '1 WEEK'; break;
case '1m': sqlInterval = '1 MONTH'; break;
case '3m': sqlInterval = '3 MONTH'; break;
case '6m': sqlInterval = '6 MONTH'; break;
case '1y': sqlInterval = '1 YEAR'; break;
case '2y': sqlInterval = '2 YEAR'; break;
case '3y': sqlInterval = '3 YEAR'; break;
default: sqlInterval = null; break;
}
const poolsStatistics = {};
const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(sqlInterval);
const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(sqlInterval);
const poolsStats: PoolStats[] = [];
let rank = 1;
poolsInfo.forEach((poolInfo: PoolInfo) => {
const poolStat: PoolStats = {
poolId: poolInfo.poolId, // mysql row id
name: poolInfo.name,
link: poolInfo.link,
blockCount: poolInfo.blockCount,
rank: rank++,
emptyBlocks: 0,
}
for (let i = 0; i < emptyBlocks.length; ++i) {
if (emptyBlocks[i].poolId === poolInfo.poolId) {
poolStat.emptyBlocks++;
}
}
poolsStats.push(poolStat);
});
poolsStatistics['pools'] = poolsStats;
const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
poolsStatistics['oldestIndexedBlockTimestamp'] = oldestBlock.getTime();
const blockCount: number = await BlocksRepository.$blockCount(sqlInterval);
poolsStatistics['blockCount'] = blockCount;
const blockHeightTip = await bitcoinClient.getBlockCount();
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(120, blockHeightTip);
poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate;
return poolsStatistics;
}
}
export default new Mining();

View File

@@ -0,0 +1,173 @@
import { readFileSync } from 'fs';
import { DB } from '../database';
import logger from '../logger';
import config from '../config';
interface Pool {
name: string;
link: string;
regexes: string[];
addresses: string[];
}
class PoolsParser {
/**
* Parse the pools.json file, consolidate the data and dump it into the database
*/
public async migratePoolsJson() {
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) {
return;
}
logger.debug('Importing pools.json to the database, open ./pools.json');
let poolsJson: object = {};
try {
const fileContent: string = readFileSync('./pools.json', 'utf8');
poolsJson = JSON.parse(fileContent);
} catch (e) {
logger.err('Unable to open ./pools.json, does the file exist?');
await this.insertUnknownPool();
return;
}
// First we save every entries without paying attention to pool duplication
const poolsDuplicated: Pool[] = [];
logger.debug('Parse coinbase_tags');
const coinbaseTags = Object.entries(poolsJson['coinbase_tags']);
for (let i = 0; i < coinbaseTags.length; ++i) {
poolsDuplicated.push({
'name': (<Pool>coinbaseTags[i][1]).name,
'link': (<Pool>coinbaseTags[i][1]).link,
'regexes': [coinbaseTags[i][0]],
'addresses': [],
});
}
logger.debug('Parse payout_addresses');
const addressesTags = Object.entries(poolsJson['payout_addresses']);
for (let i = 0; i < addressesTags.length; ++i) {
poolsDuplicated.push({
'name': (<Pool>addressesTags[i][1]).name,
'link': (<Pool>addressesTags[i][1]).link,
'regexes': [],
'addresses': [addressesTags[i][0]],
});
}
// Then, we find unique mining pool names
logger.debug('Identify unique mining pools');
const poolNames: string[] = [];
for (let i = 0; i < poolsDuplicated.length; ++i) {
if (poolNames.indexOf(poolsDuplicated[i].name) === -1) {
poolNames.push(poolsDuplicated[i].name);
}
}
logger.debug(`Found ${poolNames.length} unique mining pools`);
// Get existing pools from the db
const connection = await DB.pool.getConnection();
let existingPools;
try {
[existingPools] = await connection.query<any>({ sql: 'SELECT * FROM pools;', timeout: 120000 });
} catch (e) {
logger.err('Unable to get existing pools from the database, skipping pools.json import');
connection.release();
return;
}
// Finally, we generate the final consolidated pools data
const finalPoolDataAdd: Pool[] = [];
const finalPoolDataUpdate: Pool[] = [];
for (let i = 0; i < poolNames.length; ++i) {
let allAddresses: string[] = [];
let allRegexes: string[] = [];
const match = poolsDuplicated.filter((pool: Pool) => pool.name === poolNames[i]);
for (let y = 0; y < match.length; ++y) {
allAddresses = allAddresses.concat(match[y].addresses);
allRegexes = allRegexes.concat(match[y].regexes);
}
const finalPoolName = poolNames[i].replace(`'`, `''`); // To support single quote in names when doing db queries
if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) {
logger.debug(`Update '${finalPoolName}' mining pool`);
finalPoolDataUpdate.push({
'name': finalPoolName,
'link': match[0].link,
'regexes': allRegexes,
'addresses': allAddresses,
});
} else {
logger.debug(`Add '${finalPoolName}' mining pool`);
finalPoolDataAdd.push({
'name': finalPoolName,
'link': match[0].link,
'regexes': allRegexes,
'addresses': allAddresses,
});
}
}
logger.debug(`Update pools table now`);
// Add new mining pools into the database
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses) VALUES ';
for (let i = 0; i < finalPoolDataAdd.length; ++i) {
queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}',
'${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}'),`;
}
queryAdd = queryAdd.slice(0, -1) + ';';
// Add new mining pools into the database
const updateQueries: string[] = [];
for (let i = 0; i < finalPoolDataUpdate.length; ++i) {
updateQueries.push(`
UPDATE pools
SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}',
regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}'
WHERE name='${finalPoolDataUpdate[i].name}'
;`);
}
try {
if (finalPoolDataAdd.length > 0) {
await connection.query<any>({ sql: queryAdd, timeout: 120000 });
}
for (const query of updateQueries) {
await connection.query<any>({ sql: query, timeout: 120000 });
}
await this.insertUnknownPool();
connection.release();
logger.info('Mining pools.json import completed');
} catch (e) {
connection.release();
logger.err(`Unable to import pools in the database!`);
throw e;
}
}
/**
* Manually add the 'unknown pool'
*/
private async insertUnknownPool() {
const connection = await DB.pool.getConnection();
try {
const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 });
if (rows.length === 0) {
logger.debug('Manually inserting "Unknown" mining pool into the databse');
await connection.query({
sql: `INSERT INTO pools(name, link, regexes, addresses)
VALUES("Unknown", "https://learnmeabitcoin.com/technical/coinbase-transaction", "[]", "[]");
`});
}
} catch (e) {
logger.err('Unable to insert "Unknown" mining pool');
}
connection.release();
}
}
export default new PoolsParser();

View File

@@ -53,6 +53,17 @@ class Statistics {
memPoolArray = memPoolArray.filter((tx) => tx.effectiveFeePerVsize);
if (!memPoolArray.length) {
try {
const insertIdZeroed = await this.$createZeroedStatistic();
if (this.newStatisticsEntryCallback && insertIdZeroed) {
const newStats = await this.$get(insertIdZeroed);
if (newStats) {
this.newStatisticsEntryCallback(newStats);
}
}
} catch (e) {
logger.err('Unable to insert zeroed statistics. ' + e);
}
return;
}
@@ -83,65 +94,130 @@ class Statistics {
}
});
const insertId = await this.$create({
added: 'NOW()',
unconfirmed_transactions: memPoolArray.length,
tx_per_second: txPerSecond,
vbytes_per_second: Math.round(vBytesPerSecond),
mempool_byte_weight: totalWeight,
total_fee: totalFee,
fee_data: '',
vsize_1: weightVsizeFees['1'] || 0,
vsize_2: weightVsizeFees['2'] || 0,
vsize_3: weightVsizeFees['3'] || 0,
vsize_4: weightVsizeFees['4'] || 0,
vsize_5: weightVsizeFees['5'] || 0,
vsize_6: weightVsizeFees['6'] || 0,
vsize_8: weightVsizeFees['8'] || 0,
vsize_10: weightVsizeFees['10'] || 0,
vsize_12: weightVsizeFees['12'] || 0,
vsize_15: weightVsizeFees['15'] || 0,
vsize_20: weightVsizeFees['20'] || 0,
vsize_30: weightVsizeFees['30'] || 0,
vsize_40: weightVsizeFees['40'] || 0,
vsize_50: weightVsizeFees['50'] || 0,
vsize_60: weightVsizeFees['60'] || 0,
vsize_70: weightVsizeFees['70'] || 0,
vsize_80: weightVsizeFees['80'] || 0,
vsize_90: weightVsizeFees['90'] || 0,
vsize_100: weightVsizeFees['100'] || 0,
vsize_125: weightVsizeFees['125'] || 0,
vsize_150: weightVsizeFees['150'] || 0,
vsize_175: weightVsizeFees['175'] || 0,
vsize_200: weightVsizeFees['200'] || 0,
vsize_250: weightVsizeFees['250'] || 0,
vsize_300: weightVsizeFees['300'] || 0,
vsize_350: weightVsizeFees['350'] || 0,
vsize_400: weightVsizeFees['400'] || 0,
vsize_500: weightVsizeFees['500'] || 0,
vsize_600: weightVsizeFees['600'] || 0,
vsize_700: weightVsizeFees['700'] || 0,
vsize_800: weightVsizeFees['800'] || 0,
vsize_900: weightVsizeFees['900'] || 0,
vsize_1000: weightVsizeFees['1000'] || 0,
vsize_1200: weightVsizeFees['1200'] || 0,
vsize_1400: weightVsizeFees['1400'] || 0,
vsize_1600: weightVsizeFees['1600'] || 0,
vsize_1800: weightVsizeFees['1800'] || 0,
vsize_2000: weightVsizeFees['2000'] || 0,
});
try {
const insertId = await this.$create({
added: 'NOW()',
unconfirmed_transactions: memPoolArray.length,
tx_per_second: txPerSecond,
vbytes_per_second: Math.round(vBytesPerSecond),
mempool_byte_weight: totalWeight,
total_fee: totalFee,
fee_data: '',
vsize_1: weightVsizeFees['1'] || 0,
vsize_2: weightVsizeFees['2'] || 0,
vsize_3: weightVsizeFees['3'] || 0,
vsize_4: weightVsizeFees['4'] || 0,
vsize_5: weightVsizeFees['5'] || 0,
vsize_6: weightVsizeFees['6'] || 0,
vsize_8: weightVsizeFees['8'] || 0,
vsize_10: weightVsizeFees['10'] || 0,
vsize_12: weightVsizeFees['12'] || 0,
vsize_15: weightVsizeFees['15'] || 0,
vsize_20: weightVsizeFees['20'] || 0,
vsize_30: weightVsizeFees['30'] || 0,
vsize_40: weightVsizeFees['40'] || 0,
vsize_50: weightVsizeFees['50'] || 0,
vsize_60: weightVsizeFees['60'] || 0,
vsize_70: weightVsizeFees['70'] || 0,
vsize_80: weightVsizeFees['80'] || 0,
vsize_90: weightVsizeFees['90'] || 0,
vsize_100: weightVsizeFees['100'] || 0,
vsize_125: weightVsizeFees['125'] || 0,
vsize_150: weightVsizeFees['150'] || 0,
vsize_175: weightVsizeFees['175'] || 0,
vsize_200: weightVsizeFees['200'] || 0,
vsize_250: weightVsizeFees['250'] || 0,
vsize_300: weightVsizeFees['300'] || 0,
vsize_350: weightVsizeFees['350'] || 0,
vsize_400: weightVsizeFees['400'] || 0,
vsize_500: weightVsizeFees['500'] || 0,
vsize_600: weightVsizeFees['600'] || 0,
vsize_700: weightVsizeFees['700'] || 0,
vsize_800: weightVsizeFees['800'] || 0,
vsize_900: weightVsizeFees['900'] || 0,
vsize_1000: weightVsizeFees['1000'] || 0,
vsize_1200: weightVsizeFees['1200'] || 0,
vsize_1400: weightVsizeFees['1400'] || 0,
vsize_1600: weightVsizeFees['1600'] || 0,
vsize_1800: weightVsizeFees['1800'] || 0,
vsize_2000: weightVsizeFees['2000'] || 0,
});
if (this.newStatisticsEntryCallback && insertId) {
const newStats = await this.$get(insertId);
if (newStats) {
this.newStatisticsEntryCallback(newStats);
if (this.newStatisticsEntryCallback && insertId) {
const newStats = await this.$get(insertId);
if (newStats) {
this.newStatisticsEntryCallback(newStats);
}
}
} catch (e) {
logger.err('Unable to insert statistics. ' + e);
}
}
private async $createZeroedStatistic(): Promise<number | undefined> {
const connection = await DB.pool.getConnection();
try {
const query = `INSERT INTO statistics(
added,
unconfirmed_transactions,
tx_per_second,
vbytes_per_second,
mempool_byte_weight,
fee_data,
total_fee,
vsize_1,
vsize_2,
vsize_3,
vsize_4,
vsize_5,
vsize_6,
vsize_8,
vsize_10,
vsize_12,
vsize_15,
vsize_20,
vsize_30,
vsize_40,
vsize_50,
vsize_60,
vsize_70,
vsize_80,
vsize_90,
vsize_100,
vsize_125,
vsize_150,
vsize_175,
vsize_200,
vsize_250,
vsize_300,
vsize_350,
vsize_400,
vsize_500,
vsize_600,
vsize_700,
vsize_800,
vsize_900,
vsize_1000,
vsize_1200,
vsize_1400,
vsize_1600,
vsize_1800,
vsize_2000
)
VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)`;
const [result]: any = await connection.query(query);
connection.release();
return result.insertId;
} catch (e) {
connection.release();
logger.err('$create() error' + (e instanceof Error ? e.message : e));
}
}
private async $create(statistics: Statistic): Promise<number | undefined> {
const connection = await DB.pool.getConnection();
try {
const connection = await DB.pool.getConnection();
const query = `INSERT INTO statistics(
added,
unconfirmed_transactions,
@@ -242,6 +318,7 @@ class Statistics {
connection.release();
return result.insertId;
} catch (e) {
connection.release();
logger.err('$create() error' + (e instanceof Error ? e.message : e));
}
}

View File

@@ -44,6 +44,14 @@ class TransactionUtils {
}
return transactionExtended;
}
public hex2ascii(hex: string) {
let str = '';
for (let i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
}
}
export default new TransactionUtils();

View File

@@ -14,6 +14,7 @@ interface IConfig {
BLOCK_WEIGHT_UNITS: number;
INITIAL_BLOCKS_AMOUNT: number;
MEMPOOL_BLOCKS_AMOUNT: number;
INDEXING_BLOCKS_AMOUNT: number;
PRICE_FEED_UPDATE_INTERVAL: number;
USE_SECOND_NODE_FOR_MINFEE: boolean;
EXTERNAL_ASSETS: string[];
@@ -77,9 +78,12 @@ const defaults: IConfig = {
'BLOCK_WEIGHT_UNITS': 4000000,
'INITIAL_BLOCKS_AMOUNT': 8,
'MEMPOOL_BLOCKS_AMOUNT': 8,
'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks
'PRICE_FEED_UPDATE_INTERVAL': 3600,
'USE_SECOND_NODE_FOR_MINFEE': false,
'EXTERNAL_ASSETS': [],
'EXTERNAL_ASSETS': [
'https://mempool.space/resources/pools.json'
]
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',

View File

@@ -22,6 +22,7 @@ import loadingIndicators from './api/loading-indicators';
import mempool from './api/mempool';
import elementsParser from './api/liquid/elements-parser';
import databaseMigration from './api/database-migration';
import poolsParser from './api/pools-parser';
import syncAssets from './sync-assets';
import icons from './api/liquid/icons';
import { Common } from './api/common';
@@ -88,6 +89,7 @@ class Server {
await checkDbConnection();
try {
await databaseMigration.$initializeOrMigrateDatabase();
await poolsParser.migratePoolsJson();
} catch (e) {
throw new Error(e instanceof Error ? e.message : 'Error');
}
@@ -136,6 +138,8 @@ class Server {
}
await blocks.$updateBlocks();
await memPool.$updateMempool();
blocks.$generateBlockDatabase();
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
this.currentBackendRetryInterval = 5;
} catch (e) {
@@ -252,6 +256,7 @@ class Server {
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y'))
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools', routes.$getPools)
;
}

View File

@@ -1,5 +1,25 @@
import { IEsploraApi } from './api/bitcoin/esplora-api.interface';
export interface PoolTag {
id: number | null, // mysql row id
name: string,
link: string,
regexes: string, // JSON array
addresses: string, // JSON array
}
export interface PoolInfo {
poolId: number, // mysql row id
name: string,
link: string,
blockCount: number,
}
export interface PoolStats extends PoolInfo {
rank: number,
emptyBlocks: number,
}
export interface MempoolBlock {
blockSize: number;
blockVSize: number;

View File

@@ -0,0 +1,128 @@
import { BlockExtended, PoolTag } from '../mempool.interfaces';
import { DB } from '../database';
import logger from '../logger';
export interface EmptyBlocks {
emptyBlocks: number;
poolId: number;
}
class BlocksRepository {
/**
* Save indexed block data in the database
*/
public async $saveBlockInDatabase(
block: BlockExtended,
blockHash: string,
coinbaseHex: string | undefined,
poolTag: PoolTag
) {
const connection = await DB.pool.getConnection();
try {
const query = `INSERT INTO blocks(
height, hash, blockTimestamp, size,
weight, tx_count, coinbase_raw, difficulty,
pool_id, fees, fee_span, median_fee
) VALUE (
?, ?, FROM_UNIXTIME(?), ?,
?, ?, ?, ?,
?, ?, ?, ?
)`;
const params: any[] = [
block.height, blockHash, block.timestamp, block.size,
block.weight, block.tx_count, coinbaseHex ? coinbaseHex : '', block.difficulty,
poolTag.id, 0, '[]', block.medianFee,
];
await connection.query(query, params);
} catch (e) {
logger.err('$saveBlockInDatabase() error' + (e instanceof Error ? e.message : e));
}
connection.release();
}
/**
* Get all block height that have not been indexed between [startHeight, endHeight]
*/
public async $getMissingBlocksBetweenHeights(startHeight: number, endHeight: number): Promise<number[]> {
if (startHeight < endHeight) {
return [];
}
const connection = await DB.pool.getConnection();
const [rows] : any[] = await connection.query(`
SELECT height
FROM blocks
WHERE height <= ${startHeight} AND height >= ${endHeight}
ORDER BY height DESC;
`);
connection.release();
const indexedBlockHeights: number[] = [];
rows.forEach((row: any) => { indexedBlockHeights.push(row.height); });
const seekedBlocks: number[] = Array.from(Array(startHeight - endHeight + 1).keys(), n => n + endHeight).reverse();
const missingBlocksHeights = seekedBlocks.filter(x => indexedBlockHeights.indexOf(x) === -1);
return missingBlocksHeights;
}
/**
* Count empty blocks for all pools
*/
public async $countEmptyBlocks(interval: string | null): Promise<EmptyBlocks[]> {
const query = `
SELECT pool_id as poolId
FROM blocks
WHERE tx_count = 1` +
(interval != null ? ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``)
;
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(query);
connection.release();
return <EmptyBlocks[]>rows;
}
/**
* Get blocks count for a period
*/
public async $blockCount(interval: string | null): Promise<number> {
const query = `
SELECT count(height) as blockCount
FROM blocks` +
(interval != null ? ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``)
;
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(query);
connection.release();
return <number>rows[0].blockCount;
}
/**
* Get the oldest indexed block
*/
public async $oldestBlockTimestamp(): Promise<number> {
const connection = await DB.pool.getConnection();
const [rows]: any[] = await connection.query(`
SELECT blockTimestamp
FROM blocks
ORDER BY height
LIMIT 1;
`);
connection.release();
if (rows.length <= 0) {
return -1;
}
return <number>rows[0].blockTimestamp;
}
}
export default new BlocksRepository();

View File

@@ -0,0 +1,46 @@
import { DB } from '../database';
import { PoolInfo, PoolTag } from '../mempool.interfaces';
class PoolsRepository {
/**
* Get all pools tagging info
*/
public async $getPools(): Promise<PoolTag[]> {
const connection = await DB.pool.getConnection();
const [rows] = await connection.query('SELECT * FROM pools;');
connection.release();
return <PoolTag[]>rows;
}
/**
* Get unknown pool tagging info
*/
public async $getUnknownPool(): Promise<PoolTag> {
const connection = await DB.pool.getConnection();
const [rows] = await connection.query('SELECT * FROM pools where name = "Unknown"');
connection.release();
return <PoolTag>rows[0];
}
/**
* Get basic pool info and block count
*/
public async $getPoolsInfo(interval: string | null): Promise<PoolInfo[]> {
const query = `
SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link
FROM blocks
JOIN pools on pools.id = pool_id` +
(interval != null ? ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) +
` GROUP BY pool_id
ORDER BY COUNT(height) DESC
`;
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(query);
connection.release();
return <PoolInfo[]>rows;
}
}
export default new PoolsRepository();

View File

@@ -20,6 +20,7 @@ import { Common } from './api/common';
import bitcoinClient from './api/bitcoin/bitcoin-client';
import elementsParser from './api/liquid/elements-parser';
import icons from './api/liquid/icons';
import miningStats from './api/mining';
class Routes {
constructor() {}
@@ -531,6 +532,18 @@ class Routes {
}
}
public async $getPools(req: Request, res: Response) {
try {
let stats = await miningStats.$getPoolsStats(req.query.interval as string);
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(stats);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
public async getBlock(req: Request, res: Response) {
try {
const result = await bitcoinApi.$getBlock(req.params.hash);

View File

@@ -0,0 +1,3 @@
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022.
Signed: softsimon

3
contributors/wiz.txt Normal file
View File

@@ -0,0 +1,3 @@
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022.
Signed: wiz

View File

@@ -13,6 +13,7 @@ __MEMPOOL_RECOMMENDED_FEE_PERCENTILE__=${MEMPOOL_RECOMMENDED_FEE_PERCENTILE:=50}
__MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=1100}
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=3600}
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
@@ -74,6 +75,7 @@ sed -i "s/__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__/${__MEMPOOL_RECOMMENDED_FEE_PER
sed -i "s/__MEMPOOL_BLOCK_WEIGHT_UNITS__/${__MEMPOOL_BLOCK_WEIGHT_UNITS__}/g" mempool-config.json
sed -i "s/__MEMPOOL_INITIAL_BLOCKS_AMOUNT__/${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__/${__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__}/g" mempool-config.json
sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json
sed -i "s/__MEMPOOL_EXTERNAL_ASSETS__/${__MEMPOOL_EXTERNAL_ASSETS__}/g" mempool-config.json

View File

@@ -421,7 +421,7 @@
"link" : "http://www.dpool.top/"
},
"/Rawpool.com/": {
"name" : "Rawpool.com",
"name" : "Rawpool",
"link" : "https://www.rawpool.com/"
},
"/haominer/": {
@@ -488,10 +488,14 @@
"name" : "Binance Pool",
"link" : "https://pool.binance.com/"
},
"/Minerium.com/" : {
"/Mined in the USA by: /Minerium.com/" : {
"name" : "Minerium",
"link" : "https://www.minerium.com/"
},
"/Minerium.com/" : {
"name" : "Minerium",
"link" : "https://www.minerium.com/"
},
"/Buffett/": {
"name" : "Lubian.com",
"link" : ""
@@ -504,15 +508,15 @@
"name" : "OKKONG",
"link" : "https://hash.okkong.com"
},
"/TMSPOOL/" : {
"name" : "TMSPool",
"/AAOPOOL/" : {
"name" : "AAO Pool",
"link" : "https://btc.tmspool.top"
},
"/one_more_mcd/" : {
"name" : "EMCDPool",
"link" : "https://pool.emcd.io"
},
"/Foundry USA Pool #dropgold/" : {
"Foundry USA Pool" : {
"name" : "Foundry USA",
"link" : "https://foundrydigital.com/"
},
@@ -539,9 +543,29 @@
"/PureBTC.COM/": {
"name": "PureBTC.COM",
"link": "https://purebtc.com"
},
"MARA Pool": {
"name": "MARA Pool",
"link": "https://marapool.com"
},
"KuCoinPool": {
"name": "KuCoinPool",
"link": "https://www.kucoin.com/mining-pool/"
},
"Entrustus" : {
"name": "Entrust Charity Pool",
"link": "pool.entustus.org"
}
},
"payout_addresses" : {
"1MkCDCzHpBsYQivp8MxjY5AkTGG1f2baoe": {
"name": "Luxor",
"link": "https://mining.luxor.tech"
},
"1ArTPjj6pV3aNRhLPjJVPYoxB98VLBzUmb": {
"name" : "KuCoinPool",
"link" : "https://www.kucoin.com/mining-pool/"
},
"3Bmb9Jig8A5kHdDSxvDZ6eryj3AXd3swuJ": {
"name" : "NovaBlock",
"link" : "https://novablock.com"
@@ -606,7 +630,7 @@
"name" : "BitMinter",
"link" : "http://bitminter.com/"
},
"15xiShqUqerfjFdyfgBH1K7Gwp6cbYmsTW " : {
"15xiShqUqerfjFdyfgBH1K7Gwp6cbYmsTW" : {
"name" : "EclipseMC",
"link" : "https://eclipsemc.com/"
},
@@ -634,6 +658,14 @@
"name" : "Huobi.pool",
"link" : "https://www.hpt.com/"
},
"1BDbsWi3Mrcjp1wdop3PWFNCNZtu4R7Hjy" : {
"name" : "EMCDPool",
"link" : "https://pool.emcd.io"
},
"12QVFmJH2b4455YUHkMpEnWLeRY3eJ4Jb5" : {
"name" : "AAO Pool",
"link" : "https://btc.tmspool.top "
},
"1ALA5v7h49QT7WYLcRsxcXqXUqEqaWmkvw" : {
"name" : "CloudHashing",
"link" : "https://cloudhashing.com/"
@@ -915,7 +947,7 @@
"link" : "http://www.dpool.top/"
},
"1FbBbv5oYqFKwiPm4CAqvAy8345n8AQ74b" : {
"name" : "Rawpool.com",
"name" : "Rawpool",
"link" : "https://www.rawpool.com/"
},
"1LsFmhnne74EmU4q4aobfxfrWY4wfMVd8w" : {
@@ -934,6 +966,22 @@
"name" : "Poolin",
"link" : "https://www.poolin.com/"
},
"1E8CZo2S3CqWg1VZSJNFCTbtT8hZPuQ2kB" : {
"name" : "Poolin",
"link" : "https://www.poolin.com/"
},
"14sA8jqYQgMRQV9zUtGFvpeMEw7YDn77SK" : {
"name" : "Poolin",
"link" : "https://www.poolin.com/"
},
"1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe" : {
"name" : "Poolin",
"link" : "https://www.poolin.com/"
},
"17tUZLvy3X2557JGhceXRiij2TNYuhRr4r" : {
"name" : "Poolin",
"link" : "https://www.poolin.com/"
},
"12Taz8FFXQ3E2AGn3ZW1SZM5bLnYGX4xR6" : {
"name" : "Tangpool",
"link" : "http://www.tangpool.com/"
@@ -1126,6 +1174,10 @@
"name" : "Binance Pool",
"link" : "https://pool.binance.com/"
},
"1JvXhnHCi6XqcanvrZJ5s2Qiv4tsmm2UMy": {
"name" : "Binance Pool",
"link" : "https://pool.binance.com/"
},
"34Jpa4Eu3ApoPVUKNTN2WeuXVVq1jzxgPi": {
"name" : "Lubian.com",
"link" : "http://www.lubian.com/"
@@ -1173,6 +1225,14 @@
"3CLigLYNkrtoNgNcUwTaKoUSHCwr9W851W": {
"name": "Rawpool",
"link": "https://www.rawpool.com"
},
"bc1qf274x7penhcd8hsv3jcmwa5xxzjl2a6pa9pxwm": {
"name" : "F2Pool",
"link" : "https://www.f2pool.com/"
},
"1A32KFEX7JNPmU1PVjrtiXRrTQcesT3Nf1": {
"name": "MARA Pool",
"link": "https://marapool.com"
}
}
}
}

View File

@@ -1,87 +1,163 @@
describe('Bisq', () => {
const baseModule = Cypress.env("BASE_MODULE");
const basePath = '';
beforeEach(() => {
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');
cy.intercept('/bisq/api/markets/markets').as('markets');
cy.intercept('/bisq/api/markets/volumes/7d').as('7d');
cy.intercept('/bisq/api/markets/trades?market=all').as('trades');
cy.intercept('/bisq/api/txs/*/*').as('txs');
cy.intercept('/bisq/api/blocks/*/*').as('blocks');
cy.intercept('/bisq/api/stats').as('stats');
Cypress.Commands.add('waitForDashboard', () => {
cy.wait('@socket');
cy.wait('@hloc');
cy.wait('@ticker');
cy.wait('@markets');
cy.wait('@7d');
cy.wait('@trades');
});
const baseModule = Cypress.env("BASE_MODULE");
const basePath = '';
beforeEach(() => {
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');
cy.intercept('/bisq/api/markets/markets').as('markets');
cy.intercept('/bisq/api/markets/volumes/7d').as('7d');
cy.intercept('/bisq/api/markets/trades?market=all').as('trades');
cy.intercept('/bisq/api/txs/*/*').as('txs');
cy.intercept('/bisq/api/blocks/*/*').as('blocks');
cy.intercept('/bisq/api/stats').as('stats');
});
if (baseModule === 'bisq') {
it('loads the dashboard', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
});
if (baseModule === 'bisq') {
it('loads the dashboard', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
describe("transactions", () => {
it('loads the transactions screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('#btn-transactions').click().then(() => {
cy.get('.table > tr').should('have.length', 50);
});
});
it('loads the transactions screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.get('.table > tr').should('have.length', 50);
});
const filters = [
"Asset listing fee", "Blind vote", "Compensation request",
"Genesis", "Irregular", "Lockup", "Pay trade fee", "Proof of burn",
"Proposal", "Reimbursement request", "Transfer BSQ", "Unlock", "Vote reveal"
];
filters.forEach((filter) => {
it(`filters the transaction screen by ${filter}`, () => {
cy.visit(`${basePath}/transactions`);
cy.waitForSkeletonGone();
cy.get('#filter').click();
cy.contains(filter).find('input').click();
//TODO: change this waiter
cy.wait(1000);
cy.get('td:nth-of-type(2)').each(($td) => {
expect($td.text().trim()).to.eq(filter);
});
});
});
it('loads the blocks screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait('@blocks');
cy.get('tbody tr').should('have.length', 10);
});
it("filters using multiple criteria", () => {
const filters = ['Proposal', 'Lockup', 'Unlock'];
cy.visit(`${basePath}/transactions`);
cy.waitForSkeletonGone();
cy.get('#filter').click();
filters.forEach((filter) => {
cy.contains(filter).find('input').click();
//TODO: change this waiter
cy.wait(1000);
});
cy.get('td:nth-of-type(2)').each(($td) => {
const regex = new RegExp(`${filters.join('|')}`, 'g');
expect($td.text().trim()).to.match(regex);
});
});
it('loads the stats screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait('@stats');
});
});
const transactions = [
{ type: 'Asset listing fee', txid: '3548aa0c002b015ea700072b7d7d76d45d4f10a3573804d0d2f624c0bb255b6b' },
{ type: 'Blind vote', txid: 'f8fabb95efa1bb81325e4c961b9fc7e3508a9b9ecd4eddf1400e58867eff8d92' },
{ type: 'Compensation request', txid: 'a8cdc65fe6bb8730f5f89f99f779d0469b0a493e1ae570e20eb7afda696a18a9' },
{ type: 'Genesis', txid: '4b5417ec5ab6112bedf539c3b4f5a806ed539542d8b717e1c4470aa3180edce5' },
{ type: 'Irregular', txid: '90b06684a517388fec2237e2362a29810dc82f0e13e019c84747ec27051e6c53' },
{ type: 'Lockup', txid: '365425b3b7487229e2ba598fb8f2a9e359e3351620383e5018548649a28b78c4' },
{ type: 'Pay trade fee', txid: 'a66b30e9777e16572ab36723539df8f45bd5d8130d810b2c3d75b8c02a191eaf' },
{ type: 'Proof of burn', txid: '8325ccb87065fb9243ed9ff1cbb431fc2ac5060a60433bcde474ccbd97b76dcb' },
{ type: 'Proposal', txid: '34e2a20f045c82fbcf7cb191b42dea6fba45641777e1751ffb29d3981c4bf413' },
{ type: 'Reimbursement request', txid: '04c16c79ca6b9ec9978880024b0d0ad3100020f33286b63c85ca7b1a319421ae' },
{ type: 'Transfer BSQ', txid: '64500bd9220675ad30d5ace27de95a341a498d7eda08162ee0ce7feb8c56cb14' },
{ type: 'Unlock', txid: '5a756841bbb11137d15b0082a3fcadbe102791f41a95d661d3bd0c5ad0b3b1a3' },
{ type: 'Vote reveal', txid: 'bd7daae1d4af8837db5e47d7bd9d8b9f83dcfd35d112f85e90728b9be45191f7' }
];
it('loads the api screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.section-header').should('have.length.at.least', 1);
cy.get('.endpoint-container').should('have.length.at.least', 1);
});
transactions.forEach((transaction) => {
it(`loads a "${transaction.type}" transaction`, () => {
cy.visit(`${basePath}/tx/${transaction.txid}`);
cy.waitForSkeletonGone();
});
});
});
it('shows blocks pagination with 5 pages (desktop)', () => {
cy.viewport(760, 800);
cy.visit(`${basePath}/blocks`);
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 5 pages + 4 buttons = 9 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
describe('blocks', () => {
it('loads the blocks screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('#btn-blocks').click().then(() => {
cy.wait('@blocks');
cy.get('tbody tr').should('have.length', 10);
});
});
it('shows blocks pagination with 3 pages (mobile)', () => {
cy.viewport(669, 800);
cy.visit(`${basePath}/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('loads a specific block', () => {
cy.visit(`${basePath}/block/0000000000000000000137ef33faa63bc6e809ab30932cf77d454fb36d2bd83a`);
cy.waitForSkeletonGone();
});
});
describe('markets', () => {
it('loads the markets screen', () => {
cy.visit(`${basePath}/markets`);
cy.waitForSkeletonGone();
});
it('loads a specific market', () => {
cy.visit(`${basePath}/market/btc_eur`);
cy.waitForSkeletonGone();
//Buy Offers
cy.get('.row > :nth-child(1) td').should('have.length.at.least', 1);
//Sell offers
cy.get('.row > :nth-child(1) td').should('have.length.at.least', 1);
//Trades
cy.get('app-bisq-trades > .table-container td').should('have.length.at.least', 1);
});
});
it('loads the stats screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('#btn-stats').click().then(() => {
cy.wait('@stats');
});
});
it('loads the api screen', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('#btn-docs').click().then(() => {
cy.get('.section-header').should('have.length.at.least', 1);
cy.get('.endpoint-container').should('have.length.at.least', 1);
});
});
it('shows blocks pagination with 5 pages (desktop)', () => {
cy.viewport(760, 800);
cy.visit(`${basePath}/blocks`);
cy.waitForSkeletonGone();
cy.get('tbody tr').should('have.length', 10);
// 5 pages + 4 buttons = 9 buttons
cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9);
});
it('shows blocks pagination with 3 pages (mobile)', () => {
cy.viewport(669, 800);
cy.visit(`${basePath}/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}`);
}
});

View File

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

View File

@@ -0,0 +1,175 @@
describe('Liquid Testnet', () => {
const baseModule = Cypress.env("BASE_MODULE");
const basePath = '/testnet';
beforeEach(() => {
cy.intercept('/liquidtestnet/api/block/**').as('block');
cy.intercept('/liquidtestnet/api/blocks/').as('blocks');
cy.intercept('/liquidtestnet/api/tx/**/outspends').as('outspends');
cy.intercept('/liquidtestnet/api/block/**/txs/**').as('block-txs');
cy.intercept('/resources/pools.json').as('pools');
Cypress.Commands.add('waitForBlockData', () => {
cy.wait('@socket');
cy.wait('@block');
cy.wait('@outspends');
});
});
if (baseModule === 'liquid') {
it('check first mempool block after skeleton loads', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('#mempool-block-0 > .blockLink').should('exist');
});
it('loads the dashboard', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
});
it('loads the blocks page', () => {
cy.visit(`${basePath}`)
cy.get('#btn-blocks');
cy.waitForSkeletonGone();
});
it('loads a specific block page', () => {
cy.visit(`${basePath}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`);
cy.waitForSkeletonGone();
});
it('loads the graphs page', () => {
cy.visit(`${basePath}`);
cy.get('#btn-graphs');
cy.waitForSkeletonGone();
});
it('loads the tv page - desktop', () => {
cy.visit(`${basePath}/tv`);
cy.waitForSkeletonGone();
});
it('loads the graphs page - mobile', () => {
cy.visit(`${basePath}`)
cy.waitForSkeletonGone();
cy.viewport('iphone-6');
cy.get('.tv-only').should('not.exist');
});
it.skip('renders unconfidential addresses correctly on mobile', () => {
cy.viewport('iphone-6');
cy.visit(`${basePath}/address/__TODO__`);
cy.waitForSkeletonGone();
//TODO: Add proper IDs for these selectors
const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody';
const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)';
cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => {
cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => {
expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth));
});
});
});
describe('assets', () => {
it('shows the assets screen', () => {
cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
cy.get('table tr').should('have.length.at.least', 5);
});
it('allows searching assets', () => {
cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
cy.get('table tr').should('have.length', 1);
});
});
it('shows a specific asset ID', () => {
cy.visit(`${basePath}/assets`);
cy.waitForSkeletonGone();
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(1) a').click();
});
});
});
describe('unblinded TX', () => {
it('should not show an unblinding error message for regular txs', () => {
cy.visit(`${basePath}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`);
cy.waitForSkeletonGone();
cy.get('.error-unblinded').should('not.exist');
});
it('show unblinded TX', () => {
cy.visit(`${basePath}/tx/c3d908ab77891e4c569b0df71aae90f4720b157019ebb20db176f4f9c4d626b8#blinded=100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,df290ead654d7d110ebc5aaf0bcf11d5b5d360431a467f1cde0a856fde986893,33cb3a2fd2e76643843691cf44a78c5cd28ec652a414da752160ad63fbd37bc9,49741,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,edb0713bcbfcb3daabf601cb50978439667d208e15fed8a5ebbfea5696cda1d5,4de70115501e8c7d6bd763e229bf42781edeacf6e75e1d7bdfa4c63104bc508a`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr:nth-child(1) .amount').should('contain.text', '0.00100000 tL-BTC');
cy.get('#table-tx-vin tr').should('have.class', 'assetBox');
cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', '0.00050000 tL-BTC');
cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', '0.00049741 tL-BTC');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show empty unblinded TX', () => {
cy.visit(`${basePath}/tx/c3d908ab77891e4c569b0df71aae90f4720b157019ebb20db176f4f9c4d626b8#blinded=`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr:nth-child(1)').should('have.class', '');
cy.get('#table-tx-vin tr:nth-child(1) .amount').should('contain.text', 'Confidential');
cy.get('#table-tx-vout tr:nth-child(1)').should('have.class', '');
cy.get('#table-tx-vout tr:nth-child(2)').should('have.class', '');
cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', 'Confidential');
cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', 'Confidential');
});
it('show invalid unblinded TX hex', () => {
cy.visit(`${basePath}/tx/2477f220eef1d03f8ffa4a2861c275d155c3562adf0d79523aeeb0c59ee611ba#blinded=5000`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
cy.get('.error-unblinded').contains('Error: Invalid blinding data (invalid hex)');
});
it('show first unblinded vout', () => {
cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr:nth-child(1)').should('have.class', 'assetBox');
cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', '0.00099729 tL-BTC');
});
it('show second unblinded vout (asset)', () => {
cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`);
cy.get('#table-tx-vout tr:nth-child(2)').should('have.class', 'assetBox');
//TODO Update after the precision bug fix is merged
cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', '0 TEST');
});
it('should link to the asset page from the unblinded tx', () => {
cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`);
cy.get('#table-tx-vout tr:nth-child(2) .amount a').click().then(() => {
cy.waitForSkeletonGone();
cy.url().should('contain', '/asset/38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5');
});
});
it('show invalid error unblinded TX', () => {
cy.visit(`${basePath}/tx/c3d908ab77891e4c569b0df71aae90f4720b157019ebb20db176f4f9c4d626b8#blinded=100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,df290ead654d7d110ebc5aaf0bcf11d5b5d360431a467f1cde0a856fde986893,33cb3a2fd2e76643843691cf44a78c5cd28ec652a414da752160ad63fbd37bc9,49741,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,edb0713bcbfcb3daabf601cb50978439667d208e15fed8a5ebbfea5696cda1d5,4de70115501e8c7d6bd763e229bf42781edeacf6e75e1d7bdfa4c63104bc508c`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vin 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/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`);
cy.waitForSkeletonGone();
cy.get('#table-tx-vout tr').not('.assetBox');
cy.get('#table-tx-vin tr').not('.assetBox');
});
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,8 @@ describe('Signet', () => {
if (baseModule === 'mempool') {
it('loads the dashboard', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.visit('/signet');
cy.waitForSkeletonGone();
});
it('check first mempool block after skeleton loads', () => {
@@ -24,116 +24,116 @@ describe('Signet', () => {
});
it.skip('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/signet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
cy.mockMempoolSocket();
cy.visit("/signet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
emitMempoolInfo({
'params': {
"network": "signet"
}
});
emitMempoolInfo({
'params': {
"network": "signet"
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait(1000);
});
it('loads the pools screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('#btn-pools').click().then(() => {
cy.wait(1000);
});
});
it('loads the graphs screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('#btn-graphs').click().then(() => {
cy.wait(1000);
});
});
describe('tv mode', () => {
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.get('.chart-holder').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('.tv-only').should('not.exist');
});
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('#btn-tv').click().then(() => {
cy.get('.chart-holder').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('.tv-only').should('not.exist');
});
});
it('loads the tv screen - mobile', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('iphone-8');
cy.get('.chart-holder').should('be.visible');
//TODO: Remove comment when the bug is fixed
//cy.get('#mempool-block-0').should('be.visible');
cy.get('.tv-only').should('not.exist');
});
it('loads the tv screen - mobile', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('#btn-tv').click().then(() => {
cy.viewport('iphone-8');
cy.get('.chart-holder').should('be.visible');
cy.get('.tv-only').should('not.exist');
//TODO: Remove comment when the bug is fixed
//cy.get('#mempool-block-0').should('be.visible');
});
});
});
it('loads the api screen', () => {
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
cy.visit('/signet');
cy.waitForSkeletonGone();
cy.get('#btn-docs').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
it('shows empty blocks properly', () => {
cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/signet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
it('expands and collapses the block details', () => {
cy.visit('/signet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '13 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 43 txs
cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '13 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 43 txs
cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
});
});
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
}
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
}
});

View File

@@ -13,8 +13,8 @@ describe('Testnet', () => {
if (baseModule === 'mempool') {
it('loads the dashboard', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.visit('/testnet');
cy.waitForSkeletonGone();
});
it('check first mempool block after skeleton loads', () => {
@@ -24,113 +24,115 @@ describe('Testnet', () => {
});
it.skip('loads the dashboard with the skeleton blocks', () => {
cy.mockMempoolSocket();
cy.visit("/testnet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
cy.mockMempoolSocket();
cy.visit("/testnet");
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
cy.get('#mempool-block-0').should('be.visible');
cy.get('#mempool-block-1').should('be.visible');
cy.get('#mempool-block-2').should('be.visible');
emitMempoolInfo({
'params': {
loaded: true
}
});
emitMempoolInfo({
'params': {
loaded: true
}
});
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist');
cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist');
});
it('loads the blocks screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(2) > a').click().then(() => {
cy.wait(1000);
});
it('loads the pools screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('#btn-pools').click().then(() => {
cy.wait(1000);
});
});
it('loads the graphs screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(3) > a').click().then(() => {
cy.wait(1000);
});
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('#btn-graphs').click().then(() => {
cy.wait(1000);
});
});
describe('tv mode', () => {
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
it('loads the tv screen - desktop', () => {
cy.viewport('macbook-16');
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('#btn-tv').click().then(() => {
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
//TODO: Remove comment when the bug is fixed
//cy.get('#mempool-block-0').should('be.visible');
});
});
it('loads the tv screen - mobile', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(4) > a').click().then(() => {
cy.viewport('iphone-6');
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
it('loads the tv screen - mobile', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('#btn-tv').click().then(() => {
cy.viewport('iphone-6');
cy.wait(1000);
cy.get('.tv-only').should('not.exist');
});
});
});
it('loads the api screen', () => {
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.wait(1000);
});
cy.visit('/testnet');
cy.waitForSkeletonGone();
cy.get('#btn-docs').click().then(() => {
cy.wait(1000);
});
});
describe('blocks', () => {
it('shows empty blocks properly', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
it('shows empty blocks properly', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '1 transaction');
});
it('expands and collapses the block details', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
it('expands and collapses the block details', () => {
cy.visit('/testnet/block/0');
cy.waitForSkeletonGone();
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('be.visible');
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '11 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 48 txs
cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
cy.get('.btn.btn-outline-info').click().then(() => {
cy.get('#details').should('not.be.visible');
});
});
it('shows blocks with no pagination', () => {
cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea');
cy.waitForSkeletonGone();
cy.get('h2').invoke('text').should('equal', '11 transactions');
cy.get('ul.pagination').first().children().should('have.length', 5);
});
it('supports pagination on the block screen', () => {
// 48 txs
cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9');
cy.waitForSkeletonGone();
cy.get('.header-bg.box > a').invoke('text').then((text1) => {
cy.get('.active + li').first().click().then(() => {
cy.get('.header-bg.box > a').invoke('text').then((text2) => {
expect(text1).not.to.eq(text2);
});
});
});
});
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
}
});
} else {
it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`);
}
});

View File

@@ -1,12 +1,12 @@
{
"name": "mempool-frontend",
"version": "2.3.1",
"version": "2.4.0-dev",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mempool-frontend",
"version": "2.3.1",
"version": "2.4.0-dev",
"license": "GNU Affero General Public License v3.0",
"dependencies": {
"@angular-devkit/build-angular": "^13.1.2",
@@ -70,7 +70,7 @@
},
"optionalDependencies": {
"@cypress/schematic": "^1.3.0",
"cypress": "^9.1.1",
"cypress": "^9.3.1",
"cypress-fail-on-console-error": "^2.1.3",
"cypress-wait-until": "^1.7.1",
"mock-socket": "^9.0.3",
@@ -3801,9 +3801,9 @@
}
},
"node_modules/@types/sinonjs__fake-timers": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz",
"integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
"integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==",
"optional": true
},
"node_modules/@types/sizzle": {
@@ -5746,17 +5746,18 @@
}
},
"node_modules/cli-table3": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz",
"integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz",
"integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==",
"optional": true,
"dependencies": {
"colors": "^1.1.2",
"object-assign": "^4.1.0",
"string-width": "^4.2.0"
},
"engines": {
"node": "10.* || >= 12.*"
},
"optionalDependencies": {
"colors": "1.4.0"
}
},
"node_modules/cli-truncate": {
@@ -6786,25 +6787,26 @@
"devOptional": true
},
"node_modules/cypress": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-9.1.1.tgz",
"integrity": "sha512-yWcYD8SEQ8F3okFbRPqSDj5V0xhrZBT5QRIH+P1J2vYvtEmZ4KGciHE7LCcZZLILOrs7pg4WNCqkj/XRvReQlQ==",
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-9.3.1.tgz",
"integrity": "sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"@cypress/request": "^2.88.10",
"@cypress/xvfb": "^1.2.4",
"@types/node": "^14.14.31",
"@types/sinonjs__fake-timers": "^6.0.2",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2",
"arch": "^2.2.0",
"blob-util": "^2.0.2",
"bluebird": "3.7.2",
"bluebird": "^3.7.2",
"buffer": "^5.6.0",
"cachedir": "^2.3.0",
"chalk": "^4.1.0",
"check-more-types": "^2.24.0",
"cli-cursor": "^3.1.0",
"cli-table3": "~0.6.0",
"cli-table3": "~0.6.1",
"commander": "^5.1.0",
"common-tags": "^1.8.0",
"dayjs": "^1.10.4",
@@ -6984,16 +6986,6 @@
"node": ">=8"
}
},
"node_modules/cypress/node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"optional": true,
"dependencies": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"node_modules/cypress/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -7074,9 +7066,9 @@
}
},
"node_modules/date-format": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz",
"integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz",
"integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==",
"devOptional": true,
"engines": {
"node": ">=4.0"
@@ -8848,9 +8840,9 @@
}
},
"node_modules/flatted": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
"integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
"devOptional": true
},
"node_modules/flatten": {
@@ -8946,17 +8938,26 @@
}
},
"node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"devOptional": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=6 <7 || >=8"
"node": ">=12"
}
},
"node_modules/fs-extra/node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"devOptional": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/fs-minipass": {
@@ -10484,14 +10485,26 @@
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA=="
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"devOptional": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonfile/node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"devOptional": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
@@ -11346,21 +11359,38 @@
}
},
"node_modules/log4js": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz",
"integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==",
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.0.tgz",
"integrity": "sha512-ysc/XUecZJuN8NoKOssk3V0cQ29xY4fra6fnigZa5VwxFsCsvdqsdnEuAxNN89LlHpbE4KUD3zGcn+kFqonSVQ==",
"devOptional": true,
"dependencies": {
"date-format": "^3.0.0",
"debug": "^4.1.1",
"flatted": "^2.0.1",
"rfdc": "^1.1.4",
"streamroller": "^2.2.4"
"date-format": "^4.0.3",
"debug": "^4.3.3",
"flatted": "^3.2.4",
"rfdc": "^1.3.0",
"streamroller": "^3.0.2"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/log4js/node_modules/debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"devOptional": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -12028,9 +12058,9 @@
}
},
"node_modules/nanoid": {
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -16245,28 +16275,19 @@
}
},
"node_modules/streamroller": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz",
"integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz",
"integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==",
"devOptional": true,
"dependencies": {
"date-format": "^2.1.0",
"date-format": "^4.0.3",
"debug": "^4.1.1",
"fs-extra": "^8.1.0"
"fs-extra": "^10.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/streamroller/node_modules/date-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
"integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
"devOptional": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -17178,7 +17199,7 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">= 4.0.0"
}
@@ -20861,9 +20882,9 @@
}
},
"@types/sinonjs__fake-timers": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz",
"integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
"integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==",
"optional": true
},
"@types/sizzle": {
@@ -22536,13 +22557,12 @@
"integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q=="
},
"cli-table3": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz",
"integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz",
"integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==",
"optional": true,
"requires": {
"colors": "^1.1.2",
"object-assign": "^4.1.0",
"colors": "1.4.0",
"string-width": "^4.2.0"
}
},
@@ -23373,24 +23393,25 @@
"devOptional": true
},
"cypress": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-9.1.1.tgz",
"integrity": "sha512-yWcYD8SEQ8F3okFbRPqSDj5V0xhrZBT5QRIH+P1J2vYvtEmZ4KGciHE7LCcZZLILOrs7pg4WNCqkj/XRvReQlQ==",
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-9.3.1.tgz",
"integrity": "sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q==",
"optional": true,
"requires": {
"@cypress/request": "^2.88.10",
"@cypress/xvfb": "^1.2.4",
"@types/node": "^14.14.31",
"@types/sinonjs__fake-timers": "^6.0.2",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2",
"arch": "^2.2.0",
"blob-util": "^2.0.2",
"bluebird": "3.7.2",
"bluebird": "^3.7.2",
"buffer": "^5.6.0",
"cachedir": "^2.3.0",
"chalk": "^4.1.0",
"check-more-types": "^2.24.0",
"cli-cursor": "^3.1.0",
"cli-table3": "~0.6.0",
"cli-table3": "~0.6.1",
"commander": "^5.1.0",
"common-tags": "^1.8.0",
"dayjs": "^1.10.4",
@@ -23522,16 +23543,6 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"optional": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"optional": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -23616,9 +23627,9 @@
}
},
"date-format": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz",
"integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz",
"integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==",
"devOptional": true
},
"dayjs": {
@@ -25039,9 +25050,9 @@
}
},
"flatted": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
"integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
"devOptional": true
},
"flatten": {
@@ -25110,14 +25121,22 @@
}
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"devOptional": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"dependencies": {
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"devOptional": true
}
}
},
"fs-minipass": {
@@ -26331,12 +26350,21 @@
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA=="
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"devOptional": true,
"requires": {
"graceful-fs": "^4.1.6"
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"dependencies": {
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"devOptional": true
}
}
},
"jsonparse": {
@@ -27019,16 +27047,27 @@
}
},
"log4js": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz",
"integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==",
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.0.tgz",
"integrity": "sha512-ysc/XUecZJuN8NoKOssk3V0cQ29xY4fra6fnigZa5VwxFsCsvdqsdnEuAxNN89LlHpbE4KUD3zGcn+kFqonSVQ==",
"devOptional": true,
"requires": {
"date-format": "^3.0.0",
"debug": "^4.1.1",
"flatted": "^2.0.1",
"rfdc": "^1.1.4",
"streamroller": "^2.2.4"
"date-format": "^4.0.3",
"debug": "^4.3.3",
"flatted": "^3.2.4",
"rfdc": "^1.3.0",
"streamroller": "^3.0.2"
},
"dependencies": {
"debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"devOptional": true,
"requires": {
"ms": "2.1.2"
}
}
}
},
"lru-cache": {
@@ -27552,9 +27591,9 @@
}
},
"nanoid": {
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ=="
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="
},
"needle": {
"version": "2.9.1",
@@ -30844,22 +30883,14 @@
}
},
"streamroller": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz",
"integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz",
"integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==",
"devOptional": true,
"requires": {
"date-format": "^2.1.0",
"date-format": "^4.0.3",
"debug": "^4.1.1",
"fs-extra": "^8.1.0"
},
"dependencies": {
"date-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
"integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
"devOptional": true
}
"fs-extra": "^10.0.0"
}
},
"string_decoder": {
@@ -31585,7 +31616,7 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"devOptional": true
"dev": true
},
"unpipe": {
"version": "1.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "mempool-frontend",
"version": "2.3.1",
"version": "2.4.0-dev",
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
"license": "GNU Affero General Public License v3.0",
"homepage": "https://mempool.space",
@@ -42,9 +42,9 @@
"lint": "ng lint",
"e2e": "npm run generate-config && ng e2e",
"e2e:ci": "npm run cypress:run:ci",
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool && npm run generate-config",
"config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid && npm run generate-config",
"config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq && npm run generate-config",
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
"config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid BLOCK_WEIGHT_UNITS=300000 && npm run generate-config",
"config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
"serve:ssr": "node server.run.js",
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",
@@ -117,7 +117,7 @@
},
"optionalDependencies": {
"@cypress/schematic": "^1.3.0",
"cypress": "^9.1.1",
"cypress": "^9.3.1",
"cypress-fail-on-console-error": "^2.1.3",
"cypress-wait-until": "^1.7.1",
"mock-socket": "^9.0.3",

View File

@@ -61,10 +61,7 @@ PROXY_CONFIG = [
},
{
context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'],
target: "https://liquid.network/testnet",
pathRewrite: {
"^/api/liquidtestnet/": "/liquidtestnet/api"
},
target: "https://liquid.network",
ws: true,
secure: false,
changeOrigin: true
@@ -73,7 +70,9 @@ PROXY_CONFIG = [
if (configContent && configContent.BASE_MODULE == "liquid") {
PROXY_CONFIG.push({
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'],
context: ['/resources/pools.json',
'/resources/assets.json', '/resources/assets.minimal.json',
'/resources/assets-testnet.json', '/resources/assets-testnet.minimal.json'],
target: "https://liquid.network",
secure: false,
changeOrigin: true,

View File

@@ -6,7 +6,6 @@ import * as express from 'express';
import * as fs from 'fs';
import * as path from 'path';
import * as domino from 'domino';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
@@ -66,6 +65,7 @@ export function app(locale: string): express.Express {
server.get('/mempool-block/*', getLocalizedSSR(indexHtml));
server.get('/address/*', getLocalizedSSR(indexHtml));
server.get('/blocks', getLocalizedSSR(indexHtml));
server.get('/mining/pools', getLocalizedSSR(indexHtml));
server.get('/graphs', getLocalizedSSR(indexHtml));
server.get('/liquid', getLocalizedSSR(indexHtml));
server.get('/liquid/tx/*', getLocalizedSSR(indexHtml));
@@ -86,6 +86,7 @@ export function app(locale: string): express.Express {
server.get('/testnet/mempool-block/*', getLocalizedSSR(indexHtml));
server.get('/testnet/address/*', getLocalizedSSR(indexHtml));
server.get('/testnet/blocks', getLocalizedSSR(indexHtml));
server.get('/testnet/mining/pools', getLocalizedSSR(indexHtml));
server.get('/testnet/graphs', getLocalizedSSR(indexHtml));
server.get('/testnet/api', getLocalizedSSR(indexHtml));
server.get('/testnet/tv', getLocalizedSSR(indexHtml));
@@ -97,6 +98,7 @@ export function app(locale: string): express.Express {
server.get('/signet/mempool-block/*', getLocalizedSSR(indexHtml));
server.get('/signet/address/*', getLocalizedSSR(indexHtml));
server.get('/signet/blocks', getLocalizedSSR(indexHtml));
server.get('/signet/mining/pools', getLocalizedSSR(indexHtml));
server.get('/signet/graphs', getLocalizedSSR(indexHtml));
server.get('/signet/api', getLocalizedSSR(indexHtml));
server.get('/signet/tv', getLocalizedSSR(indexHtml));

View File

@@ -22,6 +22,7 @@ import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-mast
import { SponsorComponent } from './components/sponsor/sponsor.component';
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
let routes: Routes = [
{
@@ -58,6 +59,10 @@ let routes: Routes = [
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'mining/pools',
component: PoolRankingComponent,
},
{
path: 'graphs',
component: StatisticsComponent,
@@ -142,6 +147,10 @@ let routes: Routes = [
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'mining/pools',
component: PoolRankingComponent,
},
{
path: 'graphs',
component: StatisticsComponent,
@@ -220,6 +229,10 @@ let routes: Routes = [
path: 'blocks',
component: LatestBlocksComponent,
},
{
path: 'mining/pools',
component: PoolRankingComponent,
},
{
path: 'graphs',
component: StatisticsComponent,

View File

@@ -37,6 +37,7 @@ import { IncomingTransactionsGraphComponent } from './components/incoming-transa
import { TimeSpanComponent } from './components/time-span/time-span.component';
import { SeoService } from './services/seo.service';
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component';
import { AssetComponent } from './components/asset/asset.component';
import { AssetsComponent } from './assets/assets.component';
@@ -46,8 +47,9 @@ import { SharedModule } from './shared/shared.module';
import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
import { FeesBoxComponent } from './components/fees-box/fees-box.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { DifficultyComponent } from './components/difficulty/difficulty.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
import { ApiDocsComponent } from './components/docs/api-docs.component';
import { DocsComponent } from './components/docs/docs.component';
@@ -90,6 +92,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
FeeDistributionGraphComponent,
IncomingTransactionsGraphComponent,
MempoolGraphComponent,
PoolRankingComponent,
LbtcPegsGraphComponent,
AssetComponent,
AssetsComponent,
@@ -97,6 +100,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
StatusViewComponent,
FeesBoxComponent,
DashboardComponent,
DifficultyComponent,
ApiDocsComponent,
CodeTemplateComponent,
TermsOfServiceComponent,
@@ -141,6 +145,7 @@ export class AppModule {
library.addIcons(faTv);
library.addIcons(faTachometerAlt);
library.addIcons(faCubes);
library.addIcons(faHammer);
library.addIcons(faCogs);
library.addIcons(faThList);
library.addIcons(faList);

View File

@@ -43,7 +43,7 @@
<thead>
<th i18n="Asset name header">Name</th>
<th i18n="Asset ticker header">Ticker</th>
<th i18n="Asset Issuer Domain header">Issuer domain</th>
<th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
<th i18n="Asset ID header">Asset ID</th>
</thead>
<tbody>

View File

@@ -7,7 +7,7 @@
</div>
<span class="tx-link float-left">
<a [routerLink]="['/bisq-tx' | relativeUrl, bisqTx.id]">
<a [routerLink]="['/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>

View File

@@ -1,7 +1,7 @@
<div class="container-xl" (window:resize)="onResize($event)">
<h1 style="float: left;" i18n>BSQ Transactions</h1>
<div class="d-block float-right">
<div class="d-block float-right" id="filter">
<form [formGroup]="radioGroupForm">
<ngx-bootrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootrap-multiselect>
</form>
@@ -39,7 +39,7 @@
</td>
<td><app-time-since [time]="tx.time / 1000" [fastRender]="true"></app-time-since></td>
<td class="d-none d-md-block"><a [routerLink]="['/block/' | relativeUrl, tx.blockHash]" [state]="{ data: { blockHeight: tx.blockHeight } }">{{ tx.blockHeight }}</a></td>
</tr>
</tr>
</tbody>
</table>

View File

@@ -102,7 +102,7 @@
<img class="image" src="/resources/profile/ronindojo.png" />
<span>RoninDojo</span>
</a>
<a href="https://github.com/runcitadel/dashboard" target="_blank" title="Citadel">
<a href="https://github.com/runcitadel/core" target="_blank" title="Citadel">
<img class="image" src="/resources/profile/runcitadel.svg" />
<span>Citadel</span>
</a>
@@ -220,7 +220,7 @@
<div class="copyright">
<div class="title">
Copyright &copy; 2019-2021<br>
Copyright &copy; 2019-2022<br>
The Mempool Open Source Project
</div>
<p>

View File

@@ -21,7 +21,7 @@
<tbody>
<tr>
<td i18n="asset.name|Liquid Asset name">Name</td>
<td>{{ assetContract[2] }} ({{ assetContract[1] }})</td>
<td class="assetName">{{ assetContract[2] }} ({{ assetContract[1] }})</td>
</tr>
<tr>
<td i18n="asset.precision|Liquid Asset precision">Precision</td>
@@ -35,13 +35,6 @@
<td i18n="asset.issuance-tx|Liquid Asset issuance TX">Issuance TX</td>
<td><a [routerLink]="['/tx/' | relativeUrl, asset.issuance_txin.txid]">{{ asset.issuance_txin.txid | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.issuance_txin.txid"></app-clipboard></td>
</tr>
</tbody>
</table>
</div>
<div class="w-100 d-block d-md-none"></div>
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr *ngIf="isNativeAsset && asset.chain_stats.peg_in_amount">
<td i18n="asset.pegged-in|Liquid Asset pegged-in amount">Pegged in</td>
<td>{{ formatAmount(asset.chain_stats.peg_in_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}</td>
@@ -69,6 +62,13 @@
</tbody>
</table>
</div>
<div class="w-100 d-block d-md-none"></div>
<div class="col icon-holder">
<img *ngIf="!imageError; else defaultIcon" class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + asset.asset_id + '/icon'" (error)="imageError = true">
<ng-template #defaultIcon>
<fa-icon class="defaultIcon" [icon]="['fas', 'database']" [fixedWidth]="true" size="8x"></fa-icon>
</ng-template>
</div>
</div>
</div>
@@ -109,28 +109,39 @@
<ng-template [ngIf]="isLoadingAsset && !error">
<ng-template #loadingTmpl>
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
</ng-template>
<div class="box">
<div class="row">
<ng-container *ngTemplateOutlet="loadingTmpl"></ng-container>
<ng-container *ngTemplateOutlet="loadingTmpl"></ng-container>
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td colspan="2"><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="w-100 d-block d-md-none"></div>
<div class="col icon-holder">
<fa-icon class="defaultIcon skeleton" [icon]="['fas', 'database']" [fixedWidth]="true" size="8x"></fa-icon>
</div>
</div>
</div>

View File

@@ -50,3 +50,31 @@ h1 {
}
}
.assetIcon {
height: 150px;
margin: 25px;
@media (min-width: 768px) {
height: 250px;
margin: 0;
}
}
.icon-holder {
display: flex;
justify-content: center;
align-items: center;
}
.defaultIcon {
margin: 25px;
height: 150px;
}
.defaultIcon.skeleton {
opacity: 0.5;
}
.assetName {
word-break: break-word;
white-space: normal;
}

View File

@@ -32,6 +32,7 @@ export class AssetComponent implements OnInit, OnDestroy {
isNativeAsset = false;
error: any;
mainSubscription: Subscription;
imageError = false;
totalConfirmedTxCount = 0;
loadedConfirmedTxCount = 0;

View File

@@ -27,22 +27,22 @@
<div class="navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-transactions">
<a class="nav-link" [routerLink]="['/transactions']" (click)="collapse()"><fa-icon [icon]="['fas', 'list']" [fixedWidth]="true" i18n-title="master-page.transactions" title="Transactions"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-blocks">
<a class="nav-link" [routerLink]="['/blocks']" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-stats">
<a class="nav-link" [routerLink]="['/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'file-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-docs">
<a class="nav-link" [routerLink]="['/docs']" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-about">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
</li>
</ul>

View File

@@ -0,0 +1,87 @@
<div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
<div class="card-wrapper">
<div class="card">
<div class="card-body more-padding">
<div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
<div class="item">
<h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
<div class="card-text">
<ng-container *ngTemplateOutlet="epochData.remainingBlocks === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.remainingBlocks }"></ng-container>
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
</div>
<div class="symbol"><app-time-until [time]="epochData.remainingTime" [fastRender]="true"></app-time-until></div>
</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}">
<span *ngIf="epochData.change > 0; else arrowDownDifficulty" >
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
</span>
<ng-template #arrowDownDifficulty >
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
</ng-template>
{{ 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}">
<span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" >
<fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
</span>
<ng-template #arrowDownPreviousDifficulty >
<fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
</ng-template>
{{ epochData.previousRetarget | absolute | number: '1.2-2' }} </span> %
</div>
</div>
<div class="item" *ngIf="showProgress">
<h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
<div class="card-text">{{ epochData.progress | number: '1.2-2' }} <span class="symbol">%</span></div>
<div class="progress small-bar">
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}">&nbsp;</div>
</div>
</div>
<div class="item" *ngIf="showHalving">
<h5 class="card-title" i18n="difficulty-box.next-halving">Next halving</h5>
<div class="card-text">
<ng-container *ngTemplateOutlet="epochData.blocksUntilHalving === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.blocksUntilHalving }"></ng-container>
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
</div>
<div class="symbol"><app-time-until [time]="epochData.timeUntilHalving" [fastRender]="true"></app-time-until></div>
</div>
</div>
</div>
</div>
</div>
<ng-template #loadingDifficulty>
<div class="difficulty-skeleton loading-container">
<div class="item">
<h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
</div>
</ng-template>

View File

@@ -0,0 +1,150 @@
.difficulty-adjustment-container {
display: flex;
flex-direction: row;
justify-content: space-around;
height: 76px;
.shared-block {
color: #ffffff66;
font-size: 12px;
}
.item {
padding: 0 5px;
width: 100%;
&:nth-child(1) {
display: none;
@media (min-width: 485px) {
display: table-cell;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: table-cell;
}
}
}
.card-text {
font-size: 22px;
margin-top: -9px;
position: relative;
}
}
.difficulty-skeleton {
display: flex;
justify-content: space-between;
@media (min-width: 376px) {
flex-direction: row;
}
.item {
max-width: 150px;
margin: 0;
width: -webkit-fill-available;
@media (min-width: 376px) {
margin: 0 auto 0px;
}
&:first-child{
display: none;
@media (min-width: 485px) {
display: block;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: block;
}
}
&:last-child {
margin-bottom: 0;
}
}
.card-text {
.skeleton-loader {
width: 100%;
display: block;
&:first-child {
margin: 14px auto 0;
max-width: 80px;
}
&:last-child {
margin: 10px auto 0;
max-width: 120px;
}
}
}
}
.card {
background-color: #1d1f31;
height: 100%;
}
.card-title {
color: #4a68b9;
font-size: 1rem;
}
.progress {
display: inline-flex;
width: 100%;
background-color: #2d3348;
height: 1.1rem;
max-width: 180px;
}
.skeleton-loader {
max-width: 100%;
}
.more-padding {
padding: 18px;
}
.small-bar {
height: 8px;
top: -4px;
max-width: 120px;
}
.loading-container {
min-height: 76px;
}
.main-title {
position: relative;
color: #ffffff91;
margin-top: -13px;
font-size: 10px;
text-transform: uppercase;
font-weight: 500;
text-align: center;
padding-bottom: 3px;
}
.card-wrapper {
.card {
height: auto !important;
}
.card-body {
display: flex;
flex: inherit;
text-align: center;
flex-direction: column;
justify-content: space-around;
padding: 22px 20px;
}
}
.retarget-sign {
margin-right: -3px;
font-size: 14px;
top: -2px;
position: relative;
}
.previous-retarget-sign {
margin-right: -2px;
font-size: 10px;
}

View File

@@ -0,0 +1,121 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { combineLatest, Observable, timer } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { StateService } from '../..//services/state.service';
interface EpochProgress {
base: string;
change: number;
progress: string;
remainingBlocks: number;
newDifficultyHeight: number;
colorAdjustments: string;
colorPreviousAdjustments: string;
timeAvg: string;
remainingTime: number;
previousRetarget: number;
blocksUntilHalving: number;
timeUntilHalving: number;
}
@Component({
selector: 'app-difficulty',
templateUrl: './difficulty.component.html',
styleUrls: ['./difficulty.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DifficultyComponent implements OnInit {
isLoadingWebSocket$: Observable<boolean>;
difficultyEpoch$: Observable<EpochProgress>;
@Input() showProgress: boolean = true;
@Input() showHalving: boolean = false;
constructor(
public stateService: StateService,
) { }
ngOnInit(): void {
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
this.difficultyEpoch$ = timer(0, 1000)
.pipe(
switchMap(() => combineLatest([
this.stateService.blocks$.pipe(map(([block]) => block)),
this.stateService.lastDifficultyAdjustment$,
this.stateService.previousRetarget$
])),
map(([block, DATime, previousRetarget]) => {
const now = new Date().getTime() / 1000;
const diff = now - DATime;
const blocksInEpoch = block.height % 2016;
const progress = (blocksInEpoch >= 0) ? (blocksInEpoch / 2016 * 100).toFixed(2) : `100`;
const remainingBlocks = 2016 - blocksInEpoch;
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;
}
}
const timeAvgDiff = change * 0.1;
let timeAvgMins = 10;
if (timeAvgDiff > 0) {
timeAvgMins -= Math.abs(timeAvgDiff);
} else {
timeAvgMins += Math.abs(timeAvgDiff);
}
const timeAvg = timeAvgMins.toFixed(0);
const remainingTime = (remainingBlocks * timeAvgMins * 60 * 1000) + (now * 1000);
let colorAdjustments = '#ffffff66';
if (change > 0) {
colorAdjustments = '#3bcc49';
}
if (change < 0) {
colorAdjustments = '#dc3545';
}
let colorPreviousAdjustments = '#dc3545';
if (previousRetarget) {
if (previousRetarget >= 0) {
colorPreviousAdjustments = '#3bcc49';
}
if (previousRetarget === 0) {
colorPreviousAdjustments = '#ffffff66';
}
} else {
colorPreviousAdjustments = '#ffffff66';
}
const blocksUntilHalving = block.height % 210000;
const timeUntilHalving = (blocksUntilHalving * timeAvgMins * 60 * 1000) + (now * 1000);
return {
base: `${progress}%`,
change,
progress,
remainingBlocks,
timeAvg,
colorAdjustments,
colorPreviousAdjustments,
blocksInEpoch,
newDifficultyHeight,
remainingTime,
previousRetarget,
blocksUntilHalving,
timeUntilHalving,
};
})
);
}
}

View File

@@ -28,13 +28,13 @@
<div class="navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav {{ network.val }}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-blocks">
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
</li>
<!--
@@ -42,13 +42,13 @@
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon></a>
</li>
-->
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-assets">
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
</li>
<li [hidden]="isMobile" class="nav-item mr-2" routerLinkActive="active">
<li [hidden]="isMobile" class="nav-item mr-2" routerLinkActive="active" id="btn-docs">
<a class="nav-link" [routerLink]="['/docs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-about">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
</li>
</ul>

View File

@@ -28,38 +28,22 @@
<div class="navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav {{ network.val }}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home">
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
</li>
<ng-template [ngIf]="network.val === 'bisq'" [ngIfElse]="notBisq">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" [routerLink]="['/bisq/transactions']" (click)="collapse()"><fa-icon [icon]="['fas', 'list']" [fixedWidth]="true" i18n-title="master-page.transactions" title="Transactions"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/bisq/blocks']" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/bisq/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'file-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
</li>
</ng-template>
<ng-template #notBisq>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/blocks' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cubes']" [fixedWidth]="true" i18n-title="master-page.blocks" title="Blocks"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
</li>
<li class="nav-item d-none d-lg-block" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon></a>
</li>
</ng-template>
<li *ngIf="network.val === 'liquid' || network.val === 'liquidtestnet'" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
<li class="nav-item" routerLinkActive="active" id="btn-pools">
<a class="nav-link" [routerLink]="['/mining/pools' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="master-page.mining-pools" title="Mining Pools"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
</li>
<li class="nav-item d-none d-lg-block" routerLinkActive="active" id="btn-tv">
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tv']" [fixedWidth]="true" i18n-title="master-page.tvview" title="TV view"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active" id="btn-docs">
<a class="nav-link" [routerLink]="['/docs' | relativeUrl ]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="documentation.title" title="Documentation"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<li class="nav-item" routerLinkActive="active" id="btn-about">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
</li>
</ul>

View File

@@ -0,0 +1,77 @@
<div class="container-xl">
<!-- <app-difficulty [showProgress]=false [showHalving]=true></app-difficulty> -->
<div class="hashrate-pie" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>
<div class="card-header mb-0 mb-lg-4">
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(miningStatsObservable$ | async) as miningStats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1">
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="24h"> 24h
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 3">
<input ngbButton type="radio" [value]="'3d'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3d"> 3D
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 7">
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1w"> 1W
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 30">
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1m"> 1M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 90">
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3m"> 3M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 180">
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="6m"> 6M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 365">
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1y"> 1Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 730">
<input ngbButton type="radio" [value]="'2y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="2y"> 2Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1095">
<input ngbButton type="radio" [value]="'3y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3y"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="all"> ALL
</label>
</div>
</form>
</div>
<table class="table table-borderless text-center pools-table" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
<thead>
<tr>
<th class="d-none d-md-block" i18n="mining.rank">Rank</th>
<th class=""></th>
<th class="" i18n="mining.pool-name">Name</th>
<th class="" *ngIf="this.poolsWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
<th class="" i18n="master-page.blocks">Blocks</th>
<th class="d-none d-md-block" i18n="mining.empty-blocks">Empty Blocks</th>
</tr>
</thead>
<tbody *ngIf="(miningStatsObservable$ | async) as miningStats">
<tr *ngFor="let pool of miningStats.pools">
<td class="d-none d-md-block">{{ pool.rank }}</td>
<td class="text-right"><img width="25" height="25" src="{{ pool.logo }}" onError="this.src = './resources/mining-pools/default.svg'"></td>
<td class="">{{ pool.name }}</td>
<td class="" *ngIf="this.poolsWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }}</td>
<td class="">{{ pool['blockText'] }}</td>
<td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
</tr>
<tr style="border-top: 1px solid #555">
<td class="d-none d-md-block">-</td>
<td class="text-right"><img width="25" height="25" src="./resources/mining-pools/default.svg"></td>
<td class="" i18n="mining.all-miners"><b>All miners</b></td>
<td class="" *ngIf="this.poolsWindowPreference === '24h'"><b>{{ miningStats.lastEstimatedHashrate}} {{ miningStats.miningUnits.hashrateUnit }}</b></td>
<td class=""><b>{{ miningStats.blockCount }}</b></td>
<td class="d-none d-md-block"><b>{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</b></td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,32 @@
.hashrate-pie {
height: 100%;
min-height: 400px;
@media (max-width: 767.98px) {
min-height: 300px;
}
}
.formRadioGroup {
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 830px) {
margin-left: 2%;
flex-direction: row;
float: left;
margin-top: 0px;
}
.btn-sm {
font-size: 9px;
@media (min-width: 830px) {
font-size: 14px;
}
}
}
@media (max-width: 767.98px) {
.pools-table th,
.pools-table td {
padding: .3em !important;
}
}

View File

@@ -0,0 +1,215 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { EChartsOption } from 'echarts';
import { combineLatest, Observable, of } from 'rxjs';
import { catchError, map, share, skip, startWith, switchMap, tap } from 'rxjs/operators';
import { SinglePoolStats } from 'src/app/interfaces/node-api.interface';
import { StorageService } from '../..//services/storage.service';
import { MiningService, MiningStats } from '../../services/mining.service';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-pool-ranking',
templateUrl: './pool-ranking.component.html',
styleUrls: ['./pool-ranking.component.scss'],
styles: [`
.loadingGraphs {
position: absolute;
top: 38%;
left: calc(50% - 15px);
z-index: 100;
}
`],
})
export class PoolRankingComponent implements OnInit, OnDestroy {
poolsWindowPreference: string;
radioGroupForm: FormGroup;
isLoading = true;
chartOptions: EChartsOption = {};
chartInitOptions = {
renderer: 'svg'
};
miningStatsObservable$: Observable<MiningStats>;
constructor(
private stateService: StateService,
private storageService: StorageService,
private formBuilder: FormBuilder,
private miningService: MiningService,
) {
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '1w';
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference);
}
ngOnInit(): void {
// When...
this.miningStatsObservable$ = combineLatest([
// ...a new block is mined
this.stateService.blocks$
.pipe(
// (we always receives some blocks at start so only trigger for the last one)
skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1),
),
// ...or we change the timespan
this.radioGroupForm.get('dateSpan').valueChanges
.pipe(
startWith(this.poolsWindowPreference), // (trigger when the page loads)
tap((value) => {
this.storageService.setValue('poolsWindowPreference', value);
this.poolsWindowPreference = value;
})
)
])
// ...then refresh the mining stats
.pipe(
switchMap(() => {
this.isLoading = true;
return this.miningService.getMiningStats(this.poolsWindowPreference)
.pipe(
catchError((e) => of(this.getEmptyMiningStat()))
);
}),
map(data => {
data.pools = data.pools.map((pool: SinglePoolStats) => this.formatPoolUI(pool));
return data;
}),
tap(data => {
this.isLoading = false;
this.prepareChartOptions(data);
}),
share()
);
}
ngOnDestroy(): void {
}
formatPoolUI(pool: SinglePoolStats) {
pool['blockText'] = pool.blockCount.toString() + ` (${pool.share}%)`;
return pool;
}
isMobile() {
return (window.innerWidth <= 767.98);
}
generatePoolsChartSerieData(miningStats) {
const poolShareThreshold = this.isMobile() ? 1 : 0.5; // Do not draw pools which hashrate share is lower than that
const data: object[] = [];
miningStats.pools.forEach((pool) => {
if (parseFloat(pool.share) < poolShareThreshold) {
return;
}
data.push({
value: pool.share,
name: pool.name + (this.isMobile() ? `` : ` (${pool.share}%)`),
label: {
color: '#FFFFFF',
overflow: 'break',
},
tooltip: {
backgroundColor: "#282d47",
textStyle: {
color: "#FFFFFF",
},
formatter: () => {
if (this.poolsWindowPreference === '24h') {
return `<u><b>${pool.name} (${pool.share}%)</b></u><br>` +
pool.lastEstimatedHashrate.toString() + ' PH/s' +
`<br>` + pool.blockCount.toString() + ` blocks`;
} else {
return `<u><b>${pool.name} (${pool.share}%)</b></u><br>` +
pool.blockCount.toString() + ` blocks`;
}
}
}
});
});
return data;
}
prepareChartOptions(miningStats) {
let network = this.stateService.network;
if (network === '') {
network = 'bitcoin';
}
network = network.charAt(0).toUpperCase() + network.slice(1);
this.chartOptions = {
title: {
text: $localize`:@@mining.pool-chart-title:${network}:NETWORK: mining pools share`,
subtext: $localize`:@@mining.pool-chart-sub-title:Estimated from the # of blocks mined`,
left: 'center',
textStyle: {
color: '#FFF',
},
subtextStyle: {
color: '#CCC',
fontStyle: 'italic',
}
},
tooltip: {
trigger: 'item'
},
series: [
{
top: this.isMobile() ? '5%' : '20%',
name: 'Mining pool',
type: 'pie',
radius: this.isMobile() ? ['10%', '50%'] : ['20%', '80%'],
data: this.generatePoolsChartSerieData(miningStats),
labelLine: {
lineStyle: {
width: 2,
},
},
label: {
fontSize: 14,
},
itemStyle: {
borderRadius: 2,
borderWidth: 2,
borderColor: '#000',
},
emphasis: {
itemStyle: {
borderWidth: 2,
borderColor: '#FFF',
borderRadius: 2,
shadowBlur: 80,
shadowColor: 'rgba(255, 255, 255, 0.75)',
},
labelLine: {
lineStyle: {
width: 3,
}
}
}
}
]
};
}
/**
* Default mining stats if something goes wrong
*/
getEmptyMiningStat() {
return {
lastEstimatedHashrate: 'Error',
blockCount: 0,
totalEmptyBlock: 0,
totalEmptyBlockRatio: '',
pools: [],
availableTimespanDay: 0,
miningUnits: {
hashrateDivider: 1,
hashrateUnit: '',
},
};
}
}

View File

@@ -169,15 +169,19 @@ export class StatisticsComponent implements OnInit {
* All value higher that "median * capRatio" are capped
*/
capExtremeVbytesValues() {
if (this.stateService.network.length !== 0) {
return; // Only cap on Bitcoin mainnet
}
let capRatio = 10;
if (['1m', '3m', '6m', '1y', '2y', '3y'].includes(this.graphWindowPreference)) {
capRatio = 4;
}
// Find median value
let vBytes : number[] = [];
for (let i = 0; i < this.mempoolStats.length; ++i) {
vBytes.push(this.mempoolStats[i].vbytes_per_second);
const vBytes : number[] = [];
for (const stat of this.mempoolStats) {
vBytes.push(stat.vbytes_per_second);
}
const sorted = vBytes.slice().sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
@@ -187,8 +191,8 @@ export class StatisticsComponent implements OnInit {
}
// Cap
for (let i = 0; i < this.mempoolStats.length; ++i) {
this.mempoolStats[i].vbytes_per_second = Math.min(median * capRatio, this.mempoolStats[i].vbytes_per_second);
for (const stat of this.mempoolStats) {
stat.vbytes_per_second = Math.min(median * capRatio, stat.vbytes_per_second);
}
}
}

View File

@@ -71,7 +71,7 @@
</ng-container>
</div>
</td>
<td class="text-right nowrap">
<td class="text-right nowrap amount">
<ng-template [ngIf]="vin.prevout && vin.prevout.asset && vin.prevout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
<div *ngIf="assetsMinimal && assetsMinimal[vin.prevout.asset]">
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vin.prevout }"></ng-container>
@@ -170,7 +170,7 @@
</ng-template>
</ng-template>
</td>
<td class="text-right nowrap">
<td class="text-right nowrap amount">
<ng-template [ngIf]="vout.asset && vout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
<div *ngIf="assetsMinimal && assetsMinimal[vout.asset]">
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vout }"></ng-container>
@@ -270,7 +270,7 @@
</ng-container>
<ng-template #assetBox let-item>
{{ item.value / 100000000 | number: '1.0-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }}
{{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }}
<br />
{{ assetsMinimal[item.asset][0] }}
<br />

View File

@@ -119,6 +119,10 @@ export class TransactionsListComponent implements OnInit, OnChanges {
return '0x' + (str.length % 2 ? '0' : '') + str;
}
pow(base: number, exponent: number): number {
return Math.pow(base, exponent);
}
toggleDetails() {
this.displayDetails = !this.displayDetails;
this.ref.markForCheck();

View File

@@ -11,7 +11,7 @@
</div>
</div>
<div class="col" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
<ng-container *ngTemplateOutlet="difficultyEpoch"></ng-container>
<app-difficulty></app-difficulty>
</div>
<div class="col">
<div class="card">
@@ -38,7 +38,7 @@
</div>
</div>
<div class="col" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
<ng-container *ngTemplateOutlet="difficultyEpoch"></ng-container>
<app-difficulty></app-difficulty>
</div>
<div class="col">
<div class="card graph-card">
@@ -228,84 +228,3 @@
</ng-template>
</ng-template>
</ng-template>
<ng-template #difficultyEpoch>
<div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
<div class="card-wrapper">
<div class="card">
<div class="card-body more-padding">
<div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
<div class="item">
<h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
<div class="card-text">
<ng-container *ngTemplateOutlet="epochData.remainingBlocks === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.remainingBlocks }"></ng-container>
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
</div>
<div class="symbol"><app-time-until [time]="epochData.remainingTime" [fastRender]="true"></app-time-until></div>
</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}">
<span *ngIf="epochData.change > 0; else arrowDownDifficulty" >
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
</span>
<ng-template #arrowDownDifficulty >
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
</ng-template>
{{ 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}">
<span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" >
<fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
</span>
<ng-template #arrowDownPreviousDifficulty >
<fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
</ng-template>
{{ epochData.previousRetarget | absolute | number: '1.2-2' }} </span> %
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
<div class="card-text">{{ epochData.progress | number: '1.2-2' }} <span class="symbol">%</span></div>
<div class="progress small-bar">
<div class="progress-bar" role="progressbar" style="width: 15%; background-color: #105fb0" [ngStyle]="{'width': epochData.base}">&nbsp;</div>
</div>
</div>
</div>
</div>
</div>
</div>
</ng-template>
<ng-template #loadingDifficulty>
<div class="difficulty-skeleton loading-container">
<div class="item">
<h5 class="card-title" i18n="difficulty-box.remaining">Remaining</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="difficulty-box.current-period">Current Period</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
</div>
</ng-template>

View File

@@ -243,84 +243,6 @@
max-width: 120px;
}
.difficulty-adjustment-container {
display: flex;
flex-direction: row;
justify-content: space-around;
height: 76px;
.shared-block {
color: #ffffff66;
font-size: 12px;
}
.item {
padding: 0 5px;
width: 100%;
&:nth-child(1) {
display: none;
@media (min-width: 485px) {
display: table-cell;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: table-cell;
}
}
}
.card-text {
font-size: 22px;
margin-top: -9px;
position: relative;
}
}
.difficulty-skeleton {
display: flex;
justify-content: space-between;
@media (min-width: 376px) {
flex-direction: row;
}
.item {
max-width: 150px;
margin: 0;
width: -webkit-fill-available;
@media (min-width: 376px) {
margin: 0 auto 0px;
}
&:first-child{
display: none;
@media (min-width: 485px) {
display: block;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: block;
}
}
&:last-child {
margin-bottom: 0;
}
}
.card-text {
.skeleton-loader {
width: 100%;
display: block;
&:first-child {
margin: 14px auto 0;
max-width: 80px;
}
&:last-child {
margin: 10px auto 0;
max-width: 120px;
}
}
}
}
.loading-container {
min-height: 76px;
}

View File

@@ -15,19 +15,6 @@ interface MempoolBlocksData {
size: number;
}
interface EpochProgress {
base: string;
change: number;
progress: string;
remainingBlocks: number;
newDifficultyHeight: number;
colorAdjustments: string;
colorPreviousAdjustments: string;
timeAvg: string;
remainingTime: number;
previousRetarget: number;
}
interface MempoolInfoData {
memPoolInfo: MempoolInfo;
vBytesPerSecond: number;
@@ -51,7 +38,6 @@ export class DashboardComponent implements OnInit {
network$: Observable<string>;
mempoolBlocksData$: Observable<MempoolBlocksData>;
mempoolInfoData$: Observable<MempoolInfoData>;
difficultyEpoch$: Observable<EpochProgress>;
mempoolLoadingStatus$: Observable<number>;
vBytesPerSecondLimit = 1667;
blocks$: Observable<Block[]>;
@@ -126,82 +112,6 @@ export class DashboardComponent implements OnInit {
})
);
this.difficultyEpoch$ = timer(0, 1000)
.pipe(
switchMap(() => combineLatest([
this.stateService.blocks$.pipe(map(([block]) => block)),
this.stateService.lastDifficultyAdjustment$,
this.stateService.previousRetarget$
])),
map(([block, DATime, previousRetarget]) => {
const now = new Date().getTime() / 1000;
const diff = now - DATime;
const blocksInEpoch = block.height % 2016;
const progress = (blocksInEpoch >= 0) ? (blocksInEpoch / 2016 * 100).toFixed(2) : `100`;
const remainingBlocks = 2016 - blocksInEpoch;
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;
}
}
const timeAvgDiff = change * 0.1;
let timeAvgMins = 10;
if (timeAvgDiff > 0) {
timeAvgMins -= Math.abs(timeAvgDiff);
} else {
timeAvgMins += Math.abs(timeAvgDiff);
}
const timeAvg = timeAvgMins.toFixed(0);
const remainingTime = (remainingBlocks * timeAvgMins * 60 * 1000) + (now * 1000);
let colorAdjustments = '#ffffff66';
if (change > 0) {
colorAdjustments = '#3bcc49';
}
if (change < 0) {
colorAdjustments = '#dc3545';
}
let colorPreviousAdjustments = '#dc3545';
if (previousRetarget) {
if (previousRetarget >= 0) {
colorPreviousAdjustments = '#3bcc49';
}
if (previousRetarget === 0) {
colorPreviousAdjustments = '#ffffff66';
}
} else {
colorPreviousAdjustments = '#ffffff66';
}
return {
base: `${progress}%`,
change,
progress,
remainingBlocks,
timeAvg,
colorAdjustments,
colorPreviousAdjustments,
blocksInEpoch,
newDifficultyHeight,
remainingTime,
previousRetarget,
};
})
);
this.mempoolBlocksData$ = this.stateService.mempoolBlocks$
.pipe(
map((mempoolBlocks) => {

View File

@@ -51,3 +51,32 @@ export interface LiquidPegs {
}
export interface ITranslators { [language: string]: string; }
export interface SinglePoolStats {
pooldId: number;
name: string;
link: string;
blockCount: number;
emptyBlocks: number;
rank: number;
share: string;
lastEstimatedHashrate: string;
emptyBlockRatio: string;
logo: string;
}
export interface PoolsStats {
blockCount: number;
lastEstimatedHashrate: number;
oldestIndexedBlockTimestamp: number;
pools: SinglePoolStats[];
}
export interface MiningStats {
lastEstimatedHashrate: string,
blockCount: number,
totalEmptyBlock: number,
totalEmptyBlockRatio: string,
pools: SinglePoolStats[],
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs, ITranslators } from '../interfaces/node-api.interface';
import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs, ITranslators, PoolsStats } from '../interfaces/node-api.interface';
import { Observable } from 'rxjs';
import { StateService } from './state.service';
import { WebsocketResponse } from '../interfaces/websocket.interface';
@@ -120,4 +120,12 @@ export class ApiService {
postTransaction$(hexPayload: string): Observable<any> {
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
}
listPools$(interval: string | null) : Observable<PoolsStats> {
let params = {};
if (interval) {
params = new HttpParams().set('interval', interval);
}
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + '/api/v1/mining/pools', {params});
}
}

View File

@@ -0,0 +1,98 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { PoolsStats, SinglePoolStats } from '../interfaces/node-api.interface';
import { ApiService } from '../services/api.service';
import { StateService } from './state.service';
export interface MiningUnits {
hashrateDivider: number;
hashrateUnit: string;
}
export interface MiningStats {
lastEstimatedHashrate: string;
blockCount: number;
totalEmptyBlock: number;
totalEmptyBlockRatio: string;
pools: SinglePoolStats[];
miningUnits: MiningUnits;
availableTimespanDay: number;
}
@Injectable({
providedIn: 'root'
})
export class MiningService {
constructor(
private stateService: StateService,
private apiService: ApiService,
) { }
public getMiningStats(interval: string): Observable<MiningStats> {
return this.apiService.listPools$(interval).pipe(
map(pools => this.generateMiningStats(pools))
);
}
/**
* Set the hashrate power of ten we want to display
*/
public getMiningUnits(): MiningUnits {
const powerTable = {
0: 'H/s',
3: 'kH/s',
6: 'MH/s',
9: 'GH/s',
12: 'TH/s',
15: 'PH/s',
18: 'EH/s',
};
// I think it's fine to hardcode this since we don't have x1000 hashrate jump everyday
// If we want to support the mining dashboard for testnet, we can hardcode it too
let selectedPower = 15;
if (this.stateService.network === 'testnet') {
selectedPower = 12;
}
return {
hashrateDivider: Math.pow(10, selectedPower),
hashrateUnit: powerTable[selectedPower],
};
}
private generateMiningStats(stats: PoolsStats): MiningStats {
const miningUnits = this.getMiningUnits();
const hashrateDivider = miningUnits.hashrateDivider;
const totalEmptyBlock = Object.values(stats.pools).reduce((prev, cur) => {
return prev + cur.emptyBlocks;
}, 0);
const totalEmptyBlockRatio = (totalEmptyBlock / stats.blockCount * 100).toFixed(2);
const poolsStats = stats.pools.map((poolStat) => {
return {
share: (poolStat.blockCount / stats.blockCount * 100).toFixed(2),
lastEstimatedHashrate: (poolStat.blockCount / stats.blockCount * stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
logo: `./resources/mining-pools/` + poolStat.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg',
...poolStat
};
});
const availableTimespanDay = (
(new Date().getTime() / 1000) - (stats.oldestIndexedBlockTimestamp / 1000)
) / 3600 / 24;
return {
lastEstimatedHashrate: (stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
blockCount: stats.blockCount,
totalEmptyBlock: totalEmptyBlock,
totalEmptyBlockRatio: totalEmptyBlockRatio,
pools: poolsStats,
miningUnits: miningUnits,
availableTimespanDay: availableTimespanDay,
};
}
}

View File

@@ -6,18 +6,28 @@ import { Router, ActivatedRoute } from '@angular/router';
})
export class StorageService {
constructor(private router: Router, private route: ActivatedRoute) {
let graphWindowPreference: string = this.getValue('graphWindowPreference');
this.setDefaultValueIfNeeded('graphWindowPreference', '2h');
this.setDefaultValueIfNeeded('poolsWindowPreference', '1w');
}
setDefaultValueIfNeeded(key: string, defaultValue: string) {
let graphWindowPreference: string = this.getValue(key);
if (graphWindowPreference === null) { // First visit to mempool.space
if (this.router.url.includes("graphs")) {
this.setValue('graphWindowPreference', this.route.snapshot.fragment ? this.route.snapshot.fragment : "2h");
if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'poolsWindowPreference'
) {
this.setValue(key, this.route.snapshot.fragment ? this.route.snapshot.fragment : defaultValue);
} else {
this.setValue('graphWindowPreference', "2h");
this.setValue(key, defaultValue);
}
} else if (this.router.url.includes("graphs")) { // Visit a different graphs#fragment from last visit
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
this.setValue('graphWindowPreference', this.route.snapshot.fragment);
}
} else if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'poolsWindowPreference'
) {
// Visit a different graphs#fragment from last visit
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
this.setValue(key, this.route.snapshot.fragment);
}
}
}
getValue(key: string): string {

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.2" width="135.73mm" height="135.73mm" viewBox="0 0 13573 13573" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<defs class="ClipPathGroup">
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
<rect x="0" y="0" width="13573" height="13573"/>
</clipPath>
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
<rect x="13" y="13" width="13546" height="13546"/>
</clipPath>
</defs>
<defs class="TextShapeIndex">
<g ooo:slide="id1" ooo:id-list="id3"/>
</defs>
<defs class="EmbeddedBulletChars">
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
</g>
<g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
</g>
<g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
</g>
<g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
</g>
<g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
</g>
<g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
</g>
<g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
</g>
<g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
</g>
<g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
<path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
</g>
<g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
</g>
</defs>
<g>
<g id="id2" class="Master_Slide">
<g id="bg-id2" class="Background"/>
<g id="bo-id2" class="BackgroundObjects"/>
</g>
</g>
<g class="SlideGroup">
<g>
<g id="container-id1">
<g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
<g class="Page">
<g class="com.sun.star.drawing.ClosedBezierShape">
<g id="id3">
<rect class="BoundingBox" stroke="none" fill="none" x="681" y="481" width="12413" height="12571"/>
<path fill="rgb(178,178,178)" stroke="none" d="M 3025,482 C 2802,483 2580,504 2361,546 5189,2249 7300,4524 8967,7155 9034,5734 8462,4269 7551,3076 7178,3216 6719,3095 6402,2778 6085,2461 5964,2001 6103,1629 5158,916 4079,477 3025,482 Z M 11216,3076 L 12011,6397 10553,6630 10040,8762 11984,9797 10893,11277 11678,12442 9329,11711 9765,10551 7737,9655 8084,7418 5138,8956 5027,11026 2058,10295 1178,13050 13092,13050 13092,1022 11216,3076 Z M 6921,1567 C 6911,1567 6901,1567 6891,1568 6794,1577 6710,1613 6649,1674 6486,1837 6497,2174 6751,2428 7005,2683 7342,2693 7504,2531 7667,2368 7656,2031 7402,1777 7253,1628 7075,1562 6921,1567 Z M 5212,3389 L 682,7919 C 795,8235 974,8476 1350,8597 L 5886,4061 C 5679,3826 5454,3602 5212,3389 Z M 9412,3696 L 9658,5937 10384,3696 9412,3696 Z M 5920,5680 L 5386,6631 7837,6825 5920,5680 Z"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

12
mempool.js/.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false

16
mempool.js/.eslintrc.js Normal file
View File

@@ -0,0 +1,16 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
};

6
mempool.js/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/node_modules
/lib
/dist/*
!.gitkeep
yarn-error.log

6
mempool.js/.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"printWidth": 80
}

21
mempool.js/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Miguel Medeiros
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

147
mempool.js/README-bisq.md Normal file
View File

@@ -0,0 +1,147 @@
# mempool**JS** - Bisq API
Interface to access Bisq API.
[Back to home](./README.md)
---
## **Features**
- Addresses
- [Get Address](#get-address)
- Blocks
- [Get Block](#get-block)
- [Get Blocks](#get-blocks)
- [Get Block Tip Height](#get-block-tip-height)
- Statistics
- Transactions
- [Get Transaction](#get-transaction)
- [Get Transactions](#get-transactions)
---
### **Get Address**
Returns statistics about all Bisq transactions.
[ [NodeJS Example](examples/nodejs/bisq/addresses.ts) ] [ [HTML Example](examples/html/bisq/addresses.html) ] [ [Top](#features) ]
```js
const {
bisq: { addresses },
} = mempoolJS();
const address = 'B1DgwRN92rdQ9xpEVCdXRfgeqGw9X4YtrZz';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
```
### **Get Block**
Returns all Bisq transactions that exist in a Bitcoin block.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/bisq/blocks.ts) ] [ [HTML Example](examples/html/bisq/blocks.html) ] [ [Top](#features) ]
```js
const {
bisq: { blocks },
} = mempoolJS();
const hash = '000000000000000000079aa6bfa46eb8fc20474e8673d6e8a123b211236bf82d';
const block = await blocks.getBlock({ hash });
console.log(block);
```
### **Get Blocks**
Returns `:length` Bitcoin blocks that contain Bisq transactions, starting from `:index`.
**Parameters:**
- {number} index
- {number} length
[ [NodeJS Example](examples/nodejs/bisq/blocks.ts) ] [ [HTML Example](examples/html/bisq/blocks.html) ] [ [Top](#features) ]
```js
const {
bisq: { blocks },
} = mempoolJS();
const hash = '000000000000000000079aa6bfa46eb8fc20474e8673d6e8a123b211236bf82d';
const myBlocks = await blocks.getBlocks({ index: 0, length: 1 });
console.log(myBlocks);
```
### **Get Blocks Tip Height**
Returns the most recently processed Bitcoin block height processed by Bisq.
[ [NodeJS Example](examples/nodejs/bisq/blocks.ts) ] [ [HTML Example](examples/html/bisq/blocks.html) ] [ [Top](#features) ]
```js
const {
bisq: { blocks },
} = mempoolJS();
const myBlocksHeight = await blocks.getBlocksTipHeight({
index: 0,
length: 1,
});
console.log(myBlocksHeight);
```
### **Get Stats**
Returns statistics about all Bisq transactions.
[ [NodeJS Example](examples/nodejs/bisq/statistics.ts) ] [ [HTML Example](examples/html/bisq/statistics.html) ] [ [Top](#features) ]
```js
const {
bisq: { statistics },
} = mempoolJS();
const stats = await statistics.getStats();
console.log(stats);
```
### **Get Transaction**
Returns details about a Bisq transaction.
[ [NodeJS Example](examples/nodejs/bisq/transactions.ts) ] [ [HTML Example](examples/html/bisq/transactions.html) ] [ [Top](#features) ]
```js
const {
bisq: { transactions },
} = mempoolJS();
const txid = '4b5417ec5ab6112bedf539c3b4f5a806ed539542d8b717e1c4470aa3180edce5';
const tx = await transactions.getTx({ txid });
console.log(tx);
```
### **Get Transactions**
Returns details about a Bisq transactions.
[ [NodeJS Example](examples/nodejs/bisq/transactions.ts) ] [ [HTML Example](examples/html/bisq/transactions.html) ] [ [Top](#features) ]
```js
const {
bisq: { transactions },
} = mempoolJS();
const txs = await transactions.getTxs({ index: 0, length: 1 });
console.log(txs);
```

View File

@@ -0,0 +1,748 @@
# mempool**JS** - Bitcoin API
Interface to access Bitcoin `mainet`, `testnet`, `signet` APIs.
[Back to home](./README.md)
---
## **Features**
- Addresses
- [Get Address](#get-address)
- [Get Address Txs](#get-address-txs)
- [Get Address Txs Chain](#get-address-txs-chain)
- [Get Address Txs Mempool](#get-address-txs-mempool)
- [Get Address Txs Utxo](#get-address-txs-utxo)
- Assets
- [Get Asset](#get-asset)
- [Get Asset Txs](#get-asset-txs)
- [Get Asset Supply](#get-asset-supply)
- Blocks
- [Get Block](#get-block)
- [Get Block Status](#get-block-status)
- [Get Block Txs](#get-block-txs)
- [Get Block Txids](#get-block-txids)
- [Get Block Txid](#get-block-txid)
- [Get Block Raw](#get-block-raw)
- [Get Blocks Header](#get-blocks-header)
- [Get Blocks Height](#get-blocks-height)
- [Get Blocks](#get-blocks)
- [Get Blocks Tip Height](#get-blocks-tip-height)
- [Get Blocks Tip Hash](#get-blocks-tip-hash)
- Difficulty
- [Get Difficulty Adjustment](#get-difficulty-adjustment)
- Fees
- [Get Fees Recommended](#get-fees-recommended)
- [Get Fees Mempool Blocks](#get-fees-mempool-blocks)
- Mempool
- [Get Mempool](#get-mempool)
- [Get Mempool Recent](#get-mempool-recent)
- [Get Mempool Txids](#get-mempool-txids)
- Transactions
- [Get Tx](#get-tx)
- [Get Tx Status](#get-tx-status)
- [Get Tx Hex](#get-tx-hex)
- [Get Tx Raw](#get-tx-raw)
- [Get Tx Merkle Block Proof](#get-tx-merkle-block-proof)
- [Get Tx Merkle Proof](#get-tx-merkle-proof)
- [Get Tx Outspend](#get-tx-outspend)
- [Get Tx Outspends](#get-tx-outspends)
- [Post Tx Outspends]($post-tx-outspends)
- Websocket
- [Websocket Client](#websocket-client)
- [Websocket Server](#websocket-server)
---
### **Get Address**
Returns details about an address. Available fields: `address`, `chain_stats`, and `mempool_stats`. `{chain,mempool}\_stats` each contain an object with `tx_count`, `funded_txo_count`, `funded_txo_sum`, `spent_txo_count`, and `spent_txo_sum`.
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/mempool-js/bitcoin/addresses.ts) ] [ [HTML Example](examples/html/mempool-js/bitcoin/addresses.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { addresses },
} = mempoolJS();
const address = '1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
```
### **Get Address Txs**
Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using `:last_seen_txid`.
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/mempool-js/bitcoin/addresses.ts) ] [ [HTML Example](examples/html/mempool-js/bitcoin/addresses.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { addresses },
} = mempoolJS();
const address = '1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC';
const addressTxs = await addresses.getAddressTxs({ address });
console.log(addressTxs);
```
### **Get Address Txs Chain**
Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query.
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/mempool-js/bitcoin/addresses.ts) ] [ [HTML Example](examples/html/mempool-js/bitcoin/addresses.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { addresses },
} = mempoolJS();
const address = '1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC';
const addressTxsChain = await addresses.getAddressTxsChain({ address });
console.log(addressTxsChain);
```
### **Get Address Txs Mempool**
Get unconfirmed transaction history for the specified `address/scripthash`. Returns up to 50 transactions (no paging).
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/mempool-js/bitcoin/addresses.ts) ] [ [HTML Example](examples/html/mempool-js/bitcoin/addresses.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { addresses },
} = mempoolJS();
const address = '1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC';
const addressTxsMempool = await addresses.getAddressTxsMempool({ address });
console.log(addressTxsMempool);
```
### **Get Address Txs Utxo**
Get the list of unspent transaction outputs associated with the `address/scripthash`. Available fields: `txid`, `vout`, `value`, and `status` (with the status of the funding tx).
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/mempool-js/bitcoin/addresses.ts) ] [ [HTML Example](examples/html/mempool-js/bitcoin/addresses.html) ] [ [Top](#features) ]
```js
const { addresses } = mempoolJS();
const addressTxsUtxo = await addresses.getAddressTxsUtxo('15e10745f15593a...');
console.log(addressTxsUtxo);
```
### **Get Block**
Returns details about a block. Available fields: `id`, `height`, `version`, `timestamp`, `bits`, `nonce`, `merkle_root`, `tx_count`, `size`, `weight`, and `previousblockhash`.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const block = await blocks.getBlock({ hash });
console.log(block);
```
### **Get Block Status**
Returns the confirmation status of a block. Available fields: `in_best_chain` (boolean, false for orphaned blocks), `next_best` (the hash of the next block, only available for blocks in the best chain).
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockStatus = await blocks.getBlockStatus({ hash });
console.log(blockStatus);
```
### **Get Block Txs**
Returns a list of transactions in the block (up to 25 transactions beginning at start_index). Transactions returned here do not have the status field, since all the transactions share the same block and confirmation status.
**Parameters:**
- {string} params.hash
- {number} params.start_index
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockTxs = await blocks.getBlockTxs({ hash });
console.log(blockTxs);
```
### **Get Block Txids**
Returns a list of all txids in the block.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockTxids = await blocks.getBlockTxids({ hash });
console.log(blockTxids);
```
### **Get Block Txid**
Returns the transaction at index :index within the specified block.
**Parameters:**
- {string} params.hash
- {number} params.index
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockTxid = await blocks.getBlockTxid({ hash, index: 218 });
console.log(blockTxid);
```
### **Get Block Raw**
Returns the raw block representation in binary.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockRaw = await blocks.getBlockRaw({ hash });
console.log(blockRaw);
```
### **Get Blocks Header**
Returns the hex-encoded block header.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const blockHeader = await blocks.getBlockHeader({ hash: '0000000000000000000065bda8f8a88f2e1e00d9a6887a43d640e52a4c7660f2' });
console.log(blockHeader);
```
### **Get Blocks Height**
Returns the hash of the block currently at `:height`.
**Parameters:**
- {number} height
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const blockHeight = await blocks.getBlockHeight({ height: 0 });
console.log(blockHeight);
```
### **Get Blocks**
Returns the 10 newest blocks starting at the tip or at `:start_height` if specified.
**Parameters:**
- {number} params.start_height
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const getBlocks = await blocks.getBlocks({ start_height: 9999 });
console.log(getBlocks);
```
### **Get Blocks Tip Height**
Returns the 10 newest blocks starting at the tip or at `:start_height` if specified.
**Parameters:**
- {number} params.start_height
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const blocksTipHeight = await blocks.getBlocksTipHeight();
console.log(blocksTipHeight);
```
### **Get Blocks Tip Hash**
Returns the hash of the last block.
[ [NodeJS Example](examples/nodejs/bitcoin/blocks.ts) ] [ [HTML Example](examples/html/bitcoin/blocks.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { blocks },
} = mempoolJS();
const blocksTipHash = await blocks.getBlocksTipHash();
console.log(blocksTipHash);
```
### **Get Difficulty Adjustment**
Returns the hash of the last block.
[ [NodeJS Example](examples/nodejs/bitcoin/difficulty.ts) ] [ [HTML Example](examples/html/bitcoin/difficulty.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { difficulty },
} = mempoolJS();
const difficultyAdjustment = await difficulty.getDifficultyAdjustment();
console.log(difficultyAdjustment);
```
### **Get Fees Recommended**
Returns our currently suggested fees for new transactions.
[ [NodeJS Example](examples/nodejs/bitcoin/fees.ts) ] [ [HTML Example](examples/html/bitcoin/fees.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { fees },
} = mempoolJS();
const feesRecommended = await fees.getFeesRecommended();
console.log(feesRecommended);
```
### **Get Fees Mempool Blocks**
Returns current mempool as projected blocks.
[ [NodeJS Example](examples/nodejs/bitcoin/fees.ts) ] [ [HTML Example](examples/html/bitcoin/fees.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { fees },
} = mempoolJS();
const feesMempoolBlocks = await fees.getFeesMempoolBlocks();
console.log(feesMempoolBlocks);
```
### **Get Children Pay for Parent**
Returns current mempool as projected blocks.
[ [NodeJS Example](examples/nodejs/bitcoin/fees.ts) ] [ [HTML Example](examples/html/bitcoin/fees.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { fees },
} = mempoolJS();
const txid = 'txid';
const feesCPFP = await fees.getCPFP({ txid });
console.log(feesCPFP);
```
### **Get Mempool**
Returns current mempool backlog statistics.
[ [NodeJS Example](examples/nodejs/bitcoin/mempool.ts) ] [ [HTML Example](examples/html/bitcoin/mempool.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { mempool },
} = mempoolJS();
const getMempool = await mempool.getMempool();
console.log(getMempool);
```
### **Get Mempool Recent**
Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: `txid`, `fee`, `vsize`, and `value`.
[ [NodeJS Example](examples/nodejs/bitcoin/mempool.ts) ] [ [HTML Example](examples/html/bitcoin/mempool.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { mempool },
} = mempoolJS();
const getMempoolRecent = await mempool.getMempoolRecent();
console.log(getMempoolRecent);
```
### **Get Mempool Txids**
Get the full list of txids in the mempool as an array. The order of the `txids` is arbitrary and does not match bitcoind.
[ [NodeJS Example](examples/nodejs/bitcoin/mempool.ts) ] [ [HTML Example](examples/html/bitcoin/mempool.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { mempool },
} = mempoolJS();
const getMempoolTxids = await mempool.getMempoolTxids();
console.log(getMempoolTxids);
```
### **Get Tx**
Returns details about a transaction. Available fields: `txid`, `version`, `locktime`, `size`, `weight`, `fee`, `vin`, `vout`, and `status`.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const tx = await transactions.getTx({ txid });
console.log(tx);
```
### **Get Tx Status**
Returns the confirmation status of a transaction. Available fields: `confirmed` (boolean), `block_height` (optional), and `block_hash` (optional).
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txStatus = await transactions.getTxStatus({ txid });
console.log(txStatus);
```
### **Get Tx Hex**
Returns a transaction serialized as hex.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txHex = await transactions.getTxHex({ txid });
console.log(txHex);
```
### **Get Tx Raw**
Returns a transaction as binary data.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txRaw = await transactions.getTxRaw({ txid });
console.log(txRaw);
```
### **Get Tx Merkle Block Proof**
Returns a merkle inclusion proof for the transaction using bitcoind's merkleblock format.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txMerkleBlockProof = await transactions.getTxMerkleBlockProof({ txid });
console.log(txMerkleBlockProof);
```
### **Get Tx Merkle Proof**
Returns a merkle inclusion proof for the transaction using Electrum's blockchain.transaction.get_merkle format.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txMerkleProof = await transactions.getTxMerkleProof({ txid });
console.log(txMerkleProof);
```
### **Get Tx Outspend**
Returns the spending status of a transaction output. Available fields: `spent` (boolean), `txid` (optional), `vin` (optional), and `status` (optional, the status of the spending tx).
**Parameters:**
- {string} params.txid
- {number} params.vout
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txOutspend = await transactions.getTxOutspend({
txid,
vout: 3,
});
console.log(txOutspend);
```
### **Get Tx Outspends**
Returns the spending status of all transaction outputs.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txOutspends = await transactions.getTxOutspends({ txid });
console.log(txOutspends);
```
### **Post Tx Outspends**
Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The `txid` will be returned on success.
**Parameters:**
- {string} txhex
[ [NodeJS Example](examples/nodejs/bitcoin/transactions.ts) ] [ [HTML Example](examples/html/bitcoin/transactions.html) ] [ [Top](#features) ]
```js
const {
bitcoin: { transactions },
} = mempoolJS();
const txhex = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const postTx = await transactions.postTx({ txhex });
console.log(postTx);
```
### **Websocket**
Default push: `{ action: 'want', data: ['blocks', ...] }` to express what you want pushed. Available: blocks, mempool-block, live-2h-chart, and stats.
Push transactions related to address: `{ 'track-address': '3PbJ...bF9B' }` to receive all new transactions containing that address as input or output. Returns an array of transactions. address-transactions for new mempool transactions, and block-transactions for new block confirmed transactions.
[ [NodeJS Example](examples/nodejs/bitcoin/websocket.ts) ] [ [HTML Example](examples/html/bitcoin/websocket.html) ] [ [Top](#features) ]
#### **Websocket Server**
Only use on server side apps.
```js
const { bitcoin: { websocket } } = mempoolJS();
const init = async () => {
const ws = websocket.initServer({
options: ["blocks", "stats", "mempool-blocks", "live-2h-chart"],
});
ws.on("message", function incoming(data) {
const res = JSON.parse(data.toString());
if (res.block) {
console.log(res.block);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
}
if (res.transactions) {
console.log(res.transactions);
}
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
}
});
}
init();
```
#### **Websocket Client**
Only use on browser apps.
```js
const init = async () => {
const {
bitcoin: { websocket },
} = mempoolJS();
const ws = websocket.initClient({
options: ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart'],
});
ws.addEventListener('message', function incoming({data}) {
const res = JSON.parse(data.toString());
if (res.block) {
console.log(res.block);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
}
if (res.transactions) {
console.log(res.transactions);
}
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
}
});
};
init();
```

765
mempool.js/README-liquid.md Normal file
View File

@@ -0,0 +1,765 @@
# mempool**JS** - Liquid API
Interface to access Liquid APIs.
[Back to home](./README.md)
---
## **Features**
- Addresses
- [Get Address](#get-address)
- [Get Address Txs](#get-address-txs)
- [Get Address Txs Chain](#get-address-txs-chain)
- [Get Address Txs Mempool](#get-address-txs-mempool)
- [Get Address Txs Utxo](#get-address-txs-utxo)
- Assets
- [Get Asset](#get-asset)
- [Get Asset Icon](#get-asset-icon)
- [Get Asset Txs](#get-asset-txs)
- [Get Asset Supply](#get-asset-supply)
- [Get Assets Icons](#get-assets-icons)
- Blocks
- [Get Block](#get-block)
- [Get Block Status](#get-block-status)
- [Get Block Txs](#get-block-txs)
- [Get Block Txids](#get-block-txids)
- [Get Block Txid](#get-block-txid)
- [Get Block Raw](#get-block-raw)
- [Get Blocks Height](#get-blocks-height)
- [Get Blocks](#get-blocks)
- [Get Blocks Tip Height](#get-blocks-tip-height)
- [Get Blocks Tip Hash](#get-blocks-tip-hash)
- Fees
- [Get Fees Recommended](#get-fees-recommended)
- [Get Fees Mempool Blocks](#get-fees-mempool-blocks)
- Mempool
- [Get Mempool](#get-mempool)
- [Get Mempool Recent](#get-mempool-recent)
- [Get Mempool Txids](#get-mempool-txids)
- Transactions
- [Get Tx](#get-tx)
- [Get Tx Status](#get-tx-status)
- [Get Tx Hex](#get-tx-hex)
- [Get Tx Raw](#get-tx-raw)
- [Get Tx Merkle Block Proof](#get-tx-merkle-block-proof)
- [Get Tx Merkle Proof](#get-tx-merkle-proof)
- [Get Tx Outspend](#get-tx-outspend)
- [Get Tx Outspends](#get-tx-outspends)
- [Post Tx Outspends]($post-tx-outspends)
- Websocket
- [Websocket Client](#websocket-client)
- [Websocket Server](#websocket-server)
---
### **Get Address**
Returns details about an address. Available fields: `address`, `chain_stats`, and `mempool_stats`. `{chain,mempool}\_stats` each contain an object with `tx_count`, `funded_txo_count`, `funded_txo_sum`, `spent_txo_count`, and `spent_txo_sum`.
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const {
liquid: { addresses },
} = mempoolJS();
const address = 'Go65t19hP2FuhBMYtgbdMDgdmEzNwh1i48';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
```
### **Get Address Txs**
Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using `:last_seen_txid`.
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const {
liquid: { addresses },
} = mempoolJS();
const address = 'Go65t19hP2FuhBMYtgbdMDgdmEzNwh1i48';
const addressTxs = await addresses.getAddressTxs({ address });
console.log(addressTxs);
```
### **Get Address Txs Chain**
Get confirmed transaction history for the specified address/scripthash, sorted with newest first. Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query.
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const {
liquid: { addresses },
} = mempoolJS();
const address = 'Go65t19hP2FuhBMYtgbdMDgdmEzNwh1i48';
const addressTxsChain = await addresses.getAddressTxsChain({ address });
console.log(addressTxsChain);
```
### **Get Address Txs Mempool**
Get unconfirmed transaction history for the specified `address/scripthash`. Returns up to 50 transactions (no paging).
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const {
liquid: { addresses },
} = mempoolJS();
const address = 'Go65t19hP2FuhBMYtgbdMDgdmEzNwh1i48';
const addressTxsMempool = await addresses.getAddressTxsMempool({ address });
console.log(addressTxsMempool);
```
### **Get Address Txs Utxo**
Get the list of unspent transaction outputs associated with the `address/scripthash`. Available fields: `txid`, `vout`, `value`, and `status` (with the status of the funding tx).
**Parameters:**
- {string} address
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const { addresses } = mempoolJS();
const addressTxsUtxo = await addresses.getAddressTxsUtxo('15e10745f15593a...');
console.log(addressTxsUtxo);
```
### **Get Asset**
Returns information about a Liquid asset.
**Parameters:**
- {string} asset_id
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const {
liquid: { assets },
} = mempoolJS();
const asset_id =
'6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
const asset = await assets.getAsset({ asset_id });
console.log(asset);
```
### **Get Asset Txs**
Returns transactions associated with the specified Liquid asset. For the network's native asset, returns a list of peg in, peg out, and burn transactions. For user-issued assets, returns a list of issuance, reissuance, and burn transactions. Does not include regular transactions transferring this asset.
**Parameters:**
- {string} asset_id
- {boolean} is_mempool
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const {
liquid: { assets },
} = mempoolJS();
const asset_id =
'6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
const assetTxs = await assets.getAssetTxs({ asset_id, is_mempool: false });
console.log(assetTxs);
```
### **Get Asset Supply**
Get the current total supply of the specified asset. For the native asset (L-BTC), this is calculated as `[chain,mempool]\_stats.peg_in_amount` - `[chain,mempool]\_stats.peg_out_amount` - `[chain,mempool]\_stats.burned_amount`. For issued assets, this is calculated as `[chain,mempool]\_stats.issued_amount` - `[chain,mempool]\_stats.burned_amount`. Not available for assets with blinded issuances. If `/decimal` is specified, returns the supply as a decimal according to the asset's divisibility. Otherwise, returned in base units.
**Parameters:**
- {string} asset_id
- {boolean} decimal
[ [NodeJS Example](examples/nodejs/liquid/addresses.ts) ] [ [HTML Example](examples/html/liquid/addresses.html) ] [ [Top](#features) ]
```js
const {
liquid: { assets },
} = mempoolJS();
const asset_id =
'6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
const assetSupply = await assets.getAssetSupply({ asset_id, decimal: false });
console.log(assetSupply);
```
### **Get Block**
Returns details about a block. Available fields: `id`, `height`, `version`, `timestamp`, `bits`, `nonce`, `merkle_root`, `tx_count`, `size`, `weight`, and `previousblockhash`.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const block = await blocks.getBlock({ hash });
console.log(block);
```
### **Get Block Status**
Returns the confirmation status of a block. Available fields: `in_best_chain` (boolean, false for orphaned blocks), `next_best` (the hash of the next block, only available for blocks in the best chain).
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockStatus = await blocks.getBlockStatus({ hash });
console.log(blockStatus);
```
### **Get Block Txs**
Returns a list of transactions in the block (up to 25 transactions beginning at start_index). Transactions returned here do not have the status field, since all the transactions share the same block and confirmation status.
**Parameters:**
- {string} hash
- {number} start_index
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockTxs = await blocks.getBlockTxs({ hash });
console.log(blockTxs);
```
### **Get Block Txids**
Returns a list of all txids in the block.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockTxids = await blocks.getBlockTxids({ hash });
console.log(blockTxids);
```
### **Get Block Txid**
Returns the transaction at index :index within the specified block.
**Parameters:**
- {string} hash
- {number} index
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockTxid = await blocks.getBlockTxid({ hash, index: 218 });
console.log(blockTxid);
```
### **Get Block Raw**
Returns the raw block representation in binary.
**Parameters:**
- {string} hash
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const hash = '000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const blockRaw = await blocks.getBlockRaw({ hash });
console.log(blockRaw);
```
### **Get Blocks Height**
Returns the hash of the block currently at `:height`.
**Parameters:**
- {number} height
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const blockHeight = await blocks.getBlockHeight({ height: 0 });
console.log(blockHeight);
```
### **Get Blocks**
Returns the 10 newest blocks starting at the tip or at `:start_height` if specified.
**Parameters:**
- {number} start_height
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const getBlocks = await blocks.getBlocks({ start_height: 9999 });
console.log(getBlocks);
```
### **Get Blocks Tip Height**
Returns the 10 newest blocks starting at the tip or at `:start_height` if specified.
**Parameters:**
- {number} start_height
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const blocksTipHeight = await blocks.getBlocksTipHeight();
console.log(blocksTipHeight);
```
### **Get Blocks Tip Hash**
Returns the hash of the last block.
[ [NodeJS Example](examples/nodejs/liquid/blocks.ts) ] [ [HTML Example](examples/html/liquid/blocks.html) ] [ [Top](#features) ]
```js
const {
liquid: { blocks },
} = mempoolJS();
const blocksTipHash = await blocks.getBlocksTipHash();
console.log(blocksTipHash);
```
### **Get Fees Recommended**
Returns our currently suggested fees for new transactions.
[ [NodeJS Example](examples/nodejs/liquid/fees.ts) ] [ [HTML Example](examples/html/liquid/fees.html) ] [ [Top](#features) ]
```js
const {
liquid: { fees },
} = mempoolJS();
const feesRecommended = await fees.getFeesRecommended();
console.log(feesRecommended);
```
### **Get Fees Mempool Blocks**
Returns current mempool as projected blocks.
[ [NodeJS Example](examples/nodejs/liquid/fees.ts) ] [ [HTML Example](examples/html/liquid/fees.html) ] [ [Top](#features) ]
```js
const {
liquid: { fees },
} = mempoolJS();
const feesMempoolBlocks = await fees.getFeesMempoolBlocks();
console.log(feesMempoolBlocks);
```
### **Get Mempool**
Returns current mempool backlog statistics.
[ [NodeJS Example](examples/nodejs/liquid/mempool.ts) ] [ [HTML Example](examples/html/liquid/mempool.html) ] [ [Top](#features) ]
```js
const {
liquid: { mempool },
} = mempoolJS();
const getMempool = await mempool.getMempool();
console.log(getMempool);
```
### **Get Mempool Recent**
Get a list of the last 10 transactions to enter the mempool. Each transaction object contains simplified overview data, with the following fields: `txid`, `fee`, `vsize`, and `value`.
[ [NodeJS Example](examples/nodejs/liquid/mempool.ts) ] [ [HTML Example](examples/html/liquid/mempool.html) ] [ [Top](#features) ]
```js
const {
liquid: { mempool },
} = mempoolJS();
const getMempoolRecent = await mempool.getMempoolRecent();
console.log(getMempoolRecent);
```
### **Get Mempool Txids**
Get the full list of txids in the mempool as an array. The order of the `txids` is arbitrary and does not match bitcoind.
[ [NodeJS Example](examples/nodejs/liquid/mempool.ts) ] [ [HTML Example](examples/html/liquid/mempool.html) ] [ [Top](#features) ]
```js
const {
liquid: { mempool },
} = mempoolJS();
const getMempoolTxids = await mempool.getMempoolTxids();
console.log(getMempoolTxids);
```
### **Get Tx**
Returns details about a transaction. Available fields: `txid`, `version`, `locktime`, `size`, `weight`, `fee`, `vin`, `vout`, and `status`.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const tx = await transactions.getTx({ txid });
console.log(tx);
```
### **Get Tx Status**
Returns the confirmation status of a transaction. Available fields: `confirmed` (boolean), `block_height` (optional), and `block_hash` (optional).
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txStatus = await transactions.getTxStatus({ txid });
console.log(txStatus);
```
### **Get Tx Hex**
Returns a transaction serialized as hex.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txHex = await transactions.getTxHex({ txid });
console.log(txHex);
```
### **Get Tx Raw**
Returns a transaction as binary data.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txRaw = await transactions.getTxRaw({ txid });
console.log(txRaw);
```
### **Get Tx Merkle Block Proof**
Returns a merkle inclusion proof for the transaction using bitcoind's merkleblock format.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txMerkleBlockProof = await transactions.getTxMerkleBlockProof({ txid });
console.log(txMerkleBlockProof);
```
### **Get Tx Merkle Proof**
Returns a merkle inclusion proof for the transaction using Electrum's blockchain.transaction.get_merkle format.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txMerkleProof = await transactions.getTxMerkleProof({ txid });
console.log(txMerkleProof);
```
### **Get Tx Outspend**
Returns the spending status of a transaction output. Available fields: `spent` (boolean), `txid` (optional), `vin` (optional), and `status` (optional, the status of the spending tx).
**Parameters:**
- {string} txid
- {number} vout
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txOutspend = await transactions.getTxOutspend({
txid,
vout: 3,
});
console.log(txOutspend);
```
### **Get Tx Outspends**
Returns the spending status of all transaction outputs.
**Parameters:**
- {string} txid
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txid = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const txOutspends = await transactions.getTxOutspends({ txid });
console.log(txOutspends);
```
### **Post Tx Outspends**
Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The `txid` will be returned on success.
**Parameters:**
- {string} txhex
[ [NodeJS Example](examples/nodejs/liquid/transactions.ts) ] [ [HTML Example](examples/html/liquid/transactions.html) ] [ [Top](#features) ]
```js
const {
liquid: { transactions },
} = mempoolJS();
const txhex = '15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const postTx = await transactions.postTx({ txhex });
console.log(postTx);
```
### **Websocket**
Default push: `{ action: 'want', data: ['blocks', ...] }` to express what you want pushed. Available: blocks, mempool-block, live-2h-chart, and stats.
Push transactions related to address: `{ 'track-address': '3PbJ...bF9B' }` to receive all new transactions containing that address as input or output. Returns an array of transactions. address-transactions for new mempool transactions, and block-transactions for new block confirmed transactions.
[ [NodeJS Example](examples/nodejs/liquid/websocket.ts) ] [ [HTML Example](examples/html/liquid/websocket.html) ] [ [Top](#features) ]
#### **Websocket Server**
Only use on server side apps.
```js
const { liquid: { websocket } } = mempoolJS();
const init = async () => {
const ws = websocket.initServer({
options: ["blocks", "stats", "mempool-blocks", "live-2h-chart"],
});
ws.on("message", function incoming(data) {
const res = JSON.parse(data.toString());
if (res.blocks) {
console.log(res.blocks);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
}
if (res.transactions) {
console.log(res.transactions);
}
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
}
});
}
init();
```
#### **Websocket Client**
Only use on browser apps.
```js
const init = async () => {
const {
liquid: { websocket },
} = mempoolJS();
const ws = websocket.initClient({
options: ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart'],
});
ws.addEventListener('message', function incoming({data}) {
const res = JSON.parse(data.toString());
if (res.blocks) {
console.log(res.blocks);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
}
if (res.transactions) {
console.log(res.transactions);
}
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
}
});
};
init();
```

102
mempool.js/README.md Normal file
View File

@@ -0,0 +1,102 @@
# Mempool JS API
[![npm version](https://img.shields.io/npm/v/@mempool/mempool.js.svg?style=flat-square)](https://www.npmjs.org/package/@mempool/mempool.js)
[![NPM](https://img.shields.io/david/mempool/mempool.js.svg?style=flat-square)](https://david-dm.org/mempool/mempool.js#info=dependencies)
[![Known Vulnerabilities](https://snyk.io/test/github/mempool/mempool.js/badge.svg?style=flat-square)](https://snyk.io/test/github/mempool/mempool.js)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
NPM package module for Mempool APIs.
Documentation: [https://mempool.space/api](https://mempool.space/api)
---
## **Installation**
### **ES Modules**
Install the npm module.
```bash
# npm
$ npm install @mempool/mempool.js --save
# yarn
$ yarn add @mempool/mempool.js
```
Or if you're not into package management, just [download a ZIP](https://github.com/mempool/mempool.js/archive/refs/heads/main.zip) file.
Import the module.
```js
import mempoolJS from '@mempool/mempool.js';
// default mempool.space endpoints
const { bitcoin, bisq, liquid } = mempoolJS();
// (optional) your custom endpoints
const { bitcoin, bisq, liquid } = mempoolJS({
hostname: 'mempool.space',
network: 'testnet' // 'signet' | 'testnet' | 'mainnet'
});
```
### **CommonJS**
Include the line below in the `head` tag of your html file.
```html
<script type="text/javascript" src="https://mempool.space/mempool.js"></script>
```
Call `mempoolJS()` function to access the API methods.
```js
// default mempool.space endpoints
const { bitcoin, bisq, liquid } = mempoolJS();
// (optional) your custom endpoints
const { bitcoin, bisq, liquid } = mempoolJS({
hostname: 'mempool.space',
network: 'testnet' // 'signet' | 'testnet' | 'mainnet'
});
```
---
## **Features**
- [Bitcoin](./README-bitcoin.md)
- [Addresses](./README-bitcoin.md#get-address)
- [Blocks](./README-bitcoin.md#get-blocks)
- [Difficulty Adjustment](./README-bitcoin.md#get-difficulty-adjustment)
- [Fees](./README-bitcoin.md#get-fees)
- [Mempool](./README-bitcoin.md#get-mempool)
- [Transactions](./README-bitcoin.md#get-transactions)
- [Websocket Client](./README-bitcoin.md#Websocket-Client)
- [Websocket Server](./README-bitcoin.md#Websocket-Server)
- [Bisq](./README-bisq.md#get-address)
- [Addresses](./README-bisq.md#get-address)
- [Blocks](./README-bisq.md#get-blocks)
- [Statistics](./README-bisq.md#get-statistics)
- [Transactions](./README-bisq.md#get-transactions)
- [Liquid](./README-liquid.md#get-address)
- [Addresses](./README-liquid.md#get-address)
- [Assets](./README-liquid.md#get-address)
- [Blocks](./README-liquid.md#get-address)
- [Fees](./README-liquid.md#get-address)
- [Mempool](./README-liquid.md#get-address)
- [Transactions](./README-liquid.md#get-address)
- [Websocket Client](./README-liquid.md#Websocket-Client)
- [Websocket Server](./README-liquid.md#Websocket-Server)
---
## **Contributing**
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
---
## **License** [MIT](https://choosealicense.com/licenses/mit/)

0
mempool.js/dist/.gitkeep vendored Normal file
View File

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/bisq.js"></script>
<script>
const init = async () => {
try {
const { addresses } = bisqJS();
const address = 'B1DgwRN92rdQ9xpEVCdXRfgeqGw9X4YtrZz';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/bisq.js"></script>
<script>
const init = async () => {
try {
const { blocks } = bisqJS();
const hash =
'000000000000000000079aa6bfa46eb8fc20474e8673d6e8a123b211236bf82d';
const block = await blocks.getBlock({ hash });
console.log(block);
const myBlocks = await blocks.getBlocks({ index: 0, length: 1 });
console.log(myBlocks);
const myBlocksHeight = await blocks.getBlocksTipHeight({
index: 0,
length: 1,
});
console.log(myBlocksHeight);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/bisq.js"></script>
<script>
const init = async () => {
try {
const { markets } = bisqJS();
const market = "BTC_USD";
const basecurrency = "BTC";
const allMarkets = await markets.getMarkets();
console.log(allMarkets);
const currencies = await markets.getCurrencies();
console.log(currencies);
const depth = await markets.getDepth({ market });
console.log(depth)
const hloc = await markets.getHloc({ market });
console.log(hloc);
const offers = await markets.getOffers({ market });
console.log(offers);
const ticker = await markets.getTicker({ market });
console.log(ticker);
const trades = await markets.getTrades({ market });
console.log(trades);
const volumes = await markets.getVolumes({ basecurrency, market });
console.log(volumes);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/bisq.js"></script>
<script>
const init = async () => {
try {
const { statistics } = bisqJS();
const stats = await statistics.getStats();
console.log(stats);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/bisq.js"></script>
<script>
const init = async () => {
try {
const { transactions } = bisqJS();
const txid =
'4b5417ec5ab6112bedf539c3b4f5a806ed539542d8b717e1c4470aa3180edce5';
const tx = await transactions.getTx({ txid });
console.log(tx);
const txs = await transactions.getTxs({ index: 0, length: 1 });
console.log(txs);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/liquid.js"></script>
<script>
const init = async () => {
try {
const { addresses } = liquidJS();
const address = 'Go65t19hP2FuhBMYtgbdMDgdmEzNwh1i48';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
const addressTxs = await addresses.getAddressTxs({ address });
console.log(addressTxs);
const addressTxsChain = await addresses.getAddressTxsChain({ address });
console.log(addressTxsChain);
const addressTxsMempool = await addresses.getAddressTxsMempool({
address,
});
console.log(addressTxsMempool);
const addressTxsUtxo = await addresses.getAddressTxsUtxo({ address });
console.log(addressTxsUtxo);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/liquid.js"></script>
<script>
const init = async () => {
try {
const { assets } = liquidJS();
const asset_id = 'a0c358a0f6947864af3a06f3f6a2aeb304df7fd95c922f2f22d7412399ce7691';
const asset = await assets.getAsset({ asset_id });
console.log(asset);
const assetTxs = await assets.getAssetTxs({
asset_id,
is_mempool: false,
});
console.log(assetTxs);
const assetSupply = await assets.getAssetSupply({
asset_id,
decimal: false,
});
console.log(assetSupply);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/liquid.js"></script>
<script>
const init = async () => {
try {
const { blocks } = liquidJS();
const hash =
'54f02bdec5509ea769c8be82aed51f689969b653d92a2812d5a36266cbfbc55e';
const block = await blocks.getBlock({ hash });
console.log(block);
const blockStatus = await blocks.getBlockStatus({ hash });
console.log(blockStatus);
const blockTxs = await blocks.getBlockTxs({ hash });
console.log(blockTxs);
const blockTxids = await blocks.getBlockTxids({ hash });
console.log(blockTxids);
const blockTxid = await blocks.getBlockTxid({ hash, index: 0 });
console.log(blockTxid);
const blockRaw = await blocks.getBlockRaw({ hash });
console.log(blockRaw);
const blockHeight = await blocks.getBlockHeight({ height: 0 });
console.log(blockHeight);
const getBlocks = await blocks.getBlocks({ start_height: 9999 });
console.log(getBlocks);
const blocksTipHeight = await blocks.getBlocksTipHeight();
console.log(blocksTipHeight);
const blocksTipHash = await blocks.getBlocksTipHash();
console.log(blocksTipHash);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/liquid.js"></script>
<script>
const init = async () => {
try {
const { fees } = liquidJS();
const feesRecommended = await fees.getFeesRecommended();
console.log(feesRecommended);
const feesMempoolBlocks = await fees.getFeesMempoolBlocks();
console.log(feesMempoolBlocks);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/liquid.js"></script>
<script>
const init = async () => {
try {
const { mempool } = liquidJS();
const getMempool = await mempool.getMempool();
console.log(getMempool);
const getMempoolRecent = await mempool.getMempoolRecent();
console.log(getMempoolRecent);
const getMempoolTxids = await mempool.getMempoolTxids();
console.log(getMempoolTxids);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/liquid.js"></script>
<script>
const init = async () => {
try {
const { transactions } = liquidJS();
const txid =
'f4e0cae35f8eb239c1eee9177884582aa6e4ce41e919f276617e9c7557168b53';
const tx = await transactions.getTx({ txid });
console.log(tx);
const txStatus = await transactions.getTxStatus({ txid });
console.log(txStatus);
const txHex = await transactions.getTxHex({ txid });
console.log(txHex);
const txRaw = await transactions.getTxRaw({ txid });
console.log(txRaw);
const txMerkleProof = await transactions.getTxMerkleProof({ txid });
console.log(txMerkleProof);
const txOutspend = await transactions.getTxOutspend({ txid, vout: 3 });
console.log(txOutspend);
const txOutspends = await transactions.getTxOutspends({ txid });
console.log(txOutspends);
const txMerkleBlockProof = await transactions.getTxMerkleBlockProof({ txid });
console.log(txMerkleBlockProof);
// const postTx = await transactions.postTx({ txhex });
// console.log(postTx);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../dist/liquid.js"></script>
<script>
const init = async () => {
try {
const { websocket } = liquidJS();
const ws = websocket.initClient({
options: ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart'],
});
ws.addEventListener('message', function incoming({data}) {
const res = JSON.parse(data.toString());
if (res.blocks) {
console.log(res.blocks);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
}
if (res.transactions) {
console.log(res.transactions);
}
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
}
});
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bisq: { addresses },
} = mempoolJS();
const address = 'B1DgwRN92rdQ9xpEVCdXRfgeqGw9X4YtrZz';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bisq: { blocks },
} = mempoolJS();
const hash =
'000000000000000000079aa6bfa46eb8fc20474e8673d6e8a123b211236bf82d';
const block = await blocks.getBlock({ hash });
console.log(block);
const myBlocks = await blocks.getBlocks({ index: 0, length: 1 });
console.log(myBlocks);
const myBlocksHeight = await blocks.getBlocksTipHeight({
index: 0,
length: 1,
});
console.log(myBlocksHeight);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bisq: { statistics },
} = mempoolJS();
const stats = await statistics.getStats();
console.log(stats);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bisq: { transactions },
} = mempoolJS();
const txid = 'e5a42c5eff51822eb0ab503cac0e0eadf2141089c83c9f8363210a004c6e66a7';
const tx = await transactions.getTx({ txid });
console.log(tx);
const txs = await transactions.getTxs({ index: 0, length: 1 });
console.log(txs);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bitcoin: { addresses },
} = mempoolJS();
const address = '1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
const addressTxs = await addresses.getAddressTxs({ address });
console.log(addressTxs);
const addressTxsChain = await addresses.getAddressTxsChain({ address });
console.log(addressTxsChain);
const addressTxsMempool = await addresses.getAddressTxsMempool({
address,
});
console.log(addressTxsMempool);
const addressTxsUtxo = await addresses.getAddressTxsUtxo({ address });
console.log(addressTxsUtxo);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bitcoin: { blocks },
} = mempoolJS();
const hash =
'000000000000000015dc777b3ff2611091336355d3f0ee9766a2cf3be8e4b1ce';
const block = await blocks.getBlock({ hash });
console.log(block);
const blockStatus = await blocks.getBlockStatus({ hash });
console.log(blockStatus);
const blockTxs = await blocks.getBlockTxs({ hash });
console.log(blockTxs);
const blockTxids = await blocks.getBlockTxids({ hash });
console.log(blockTxids);
const blockTxid = await blocks.getBlockTxid({ hash, index: 218 });
console.log(blockTxid);
const blockRaw = await blocks.getBlockRaw({ hash });
console.log(blockRaw);
const blockHeader = await blocks.getBlockHeader({ hash });
console.log(blockHeader);
const blockHeight = await blocks.getBlockHeight({ height: 0 });
console.log(blockHeight);
const getBlocks = await blocks.getBlocks({ start_height: 9999 });
console.log(getBlocks);
const blocksTipHeight = await blocks.getBlocksTipHeight();
console.log(blocksTipHeight);
const blocksTipHash = await blocks.getBlocksTipHash();
console.log(blocksTipHash);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bitcoin: { difficulty },
} = mempoolJS();
const difficultyAdjustment = await difficulty.getDifficultyAdjustment();
console.log(difficultyAdjustment);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bitcoin: { fees },
} = mempoolJS();
const feesRecommended = await fees.getFeesRecommended();
console.log(feesRecommended);
const feesMempoolBlocks = await fees.getFeesMempoolBlocks();
console.log(feesMempoolBlocks);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bitcoin: { mempool },
} = mempoolJS();
const getMempool = await mempool.getMempool();
console.log(getMempool);
const getMempoolRecent = await mempool.getMempoolRecent();
console.log(getMempoolRecent);
const getMempoolTxids = await mempool.getMempoolTxids();
console.log(getMempoolTxids);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bitcoin: { transactions },
} = mempoolJS();
const txid =
'15e10745f15593a899cef391191bdd3d7c12412cc4696b7bcb669d0feadc8521';
const tx = await transactions.getTx({ txid });
console.log(tx);
const txStatus = await transactions.getTxStatus({ txid });
console.log(txStatus);
const txHex = await transactions.getTxHex({ txid });
console.log(txHex);
const txRaw = await transactions.getTxRaw({ txid });
console.log(txRaw);
const txMerkleBlockProof = await transactions.getTxMerkleBlockProof({
txid,
});
console.log(txMerkleBlockProof);
const txMerkleProof = await transactions.getTxMerkleProof({ txid });
console.log(txMerkleProof);
const txOutspend = await transactions.getTxOutspend({
txid,
vout: 3,
});
console.log(txOutspend);
const txOutspends = await transactions.getTxOutspends({ txid });
console.log(txOutspends);
const postTx = await transactions.postTx({ txhex });
console.log(postTx);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
bitcoin: { websocket },
} = mempoolJS();
const ws = websocket.initClient({
options: ['blocks', 'stats', 'mempool-blocks', 'live-2h-chart'],
});
ws.addEventListener('message', function incoming({data}) {
const res = JSON.parse(data.toString());
if (res.block) {
console.log(res.block);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
}
if (res.transactions) {
console.log(res.transactions);
}
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
}
});
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
liquid: { addresses },
} = mempoolJS();
const address = 'Go65t19hP2FuhBMYtgbdMDgdmEzNwh1i48';
const myAddress = await addresses.getAddress({ address });
console.log(myAddress);
const addressTxs = await addresses.getAddressTxs({ address });
console.log(addressTxs);
const addressTxsChain = await addresses.getAddressTxsChain({ address });
console.log(addressTxsChain);
const addressTxsMempool = await addresses.getAddressTxsMempool({
address,
});
console.log(addressTxsMempool);
const addressTxsUtxo = await addresses.getAddressTxsUtxo({ address });
console.log(addressTxsUtxo);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
liquid: { assets },
} = mempoolJS();
const asset_id = 'a0c358a0f6947864af3a06f3f6a2aeb304df7fd95c922f2f22d7412399ce7691';
const asset = await assets.getAsset({ asset_id });
console.log(asset);
const assetTxs = await assets.getAssetTxs({
asset_id,
is_mempool: false,
});
console.log(assetTxs);
const assetSupply = await assets.getAssetSupply({
asset_id,
decimal: false,
});
console.log(assetSupply);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
liquid: { blocks },
} = mempoolJS();
const hash =
'54f02bdec5509ea769c8be82aed51f689969b653d92a2812d5a36266cbfbc55e';
const block = await blocks.getBlock({ hash });
console.log(block);
const blockStatus = await blocks.getBlockStatus({ hash });
console.log(blockStatus);
const blockTxs = await blocks.getBlockTxs({ hash });
console.log(blockTxs);
const blockTxids = await blocks.getBlockTxids({ hash });
console.log(blockTxids);
const blockTxid = await blocks.getBlockTxid({ hash, index: 0 });
console.log(blockTxid);
const blockRaw = await blocks.getBlockRaw({ hash });
console.log(blockRaw);
const blockHeight = await blocks.getBlockHeight({ height: 0 });
console.log(blockHeight);
const getBlocks = await blocks.getBlocks({ start_height: 9999 });
console.log(getBlocks);
const blocksTipHeight = await blocks.getBlocksTipHeight();
console.log(blocksTipHeight);
const blocksTipHash = await blocks.getBlocksTipHash();
console.log(blocksTipHash);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script src="./../../../../dist/mempool.js"></script>
<script>
const init = async () => {
try {
const {
liquid: { fees },
} = mempoolJS();
const feesRecommended = await fees.getFeesRecommended();
console.log(feesRecommended);
const feesMempoolBlocks = await fees.getFeesMempoolBlocks();
console.log(feesMempoolBlocks);
} catch (error) {
console.log(error);
}
};
init();
</script>
</head>
<body></body>
</html>

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