Compare commits

..

504 Commits

Author SHA1 Message Date
Mononaut
3b1f8ef4a2 fix custom x widget 2024-05-12 15:54:15 +00:00
Mononaut
86ef1e5fce update onbtc config 2024-05-12 15:47:00 +00:00
softsimon
b8725ab13a Merge pull request #5054 from mempool/mononaut/fix-statistics-graphs
Fix statistics graphs
2024-05-10 14:12:34 +07:00
wiz
6bf47f69eb Merge pull request #5056 from mempool/mononaut/unfurl-descriptions
temporarily hardcode unfurl titles & descriptions
2024-05-10 06:13:25 +09:00
wiz
0192c2bffa ops: Modify build script to build unfurlers last 2024-05-10 05:41:46 +09:00
Mononaut
91aa815d88 temporarily hardcode unfurl titles & descriptions 2024-05-09 20:01:13 +00:00
wiz
dcdb9361e8 Merge pull request #5055 from mempool/mononaut/custom-build-html
Update custom sv html
2024-05-10 04:22:15 +09:00
Mononaut
89531d1703 Update custom sv html 2024-05-09 19:18:17 +00:00
wiz
8311e842bb ops: Enable testnet4 in prod frontend configs 2024-05-10 04:02:35 +09:00
wiz
a492223369 Add warning that testnet4 may be reset at anytime 2024-05-10 03:57:25 +09:00
Mononaut
cd9e336165 Also fix stats on custom dashboard & tv view 2024-05-09 18:05:01 +00:00
Mononaut
5b2ecac1f6 Fix statistics graphs 2024-05-09 18:00:09 +00:00
wiz
d2f7864266 ops: Don't serve stale cache data for non-mainnet (2/2) 2024-05-10 01:14:51 +09:00
wiz
c9432aa0d1 Merge pull request #5053 from mempool/mononaut/unfurler-fixes-2
Fix unfurler resources build
2024-05-10 00:56:56 +09:00
Mononaut
eeb2bae9f3 Fix unfurler resources build 2024-05-09 15:55:03 +00:00
wiz
659ef0b6b9 ops: Don't serve stale cache data for non-mainnet 2024-05-10 00:44:53 +09:00
wiz
9b83ac29b7 Merge pull request #5050 from mempool/mononaut/t4-unfurls
Enable testnet4 unfurls
2024-05-09 23:42:39 +09:00
wiz
722b81623b Merge pull request #5051 from mempool/mononaut/fix-onbtc-fallback-img
Fix onbtc unfurler fallback image
2024-05-09 23:42:26 +09:00
wiz
648dd5adb2 Merge pull request #5052 from mempool/mononaut/testnet3-banner
Update testnet3 banner
2024-05-09 23:42:11 +09:00
Mononaut
75e7bb3255 Update testnet3 banner 2024-05-08 20:46:55 +00:00
Mononaut
10fc4d27ad Fix onbtc unfurler fallback image 2024-05-08 19:59:50 +00:00
Mononaut
ace422c0d5 Enable testnet4 unfurls 2024-05-08 19:58:57 +00:00
softsimon
b6682eed2a Merge pull request #5048 from mempool/dependabot/npm_and_yarn/frontend/cypress-13.9.0
Bump cypress from 13.8.0 to 13.9.0 in /frontend
2024-05-09 00:09:55 +07:00
softsimon
8080b8b56a Remove Bukele theme option 2024-05-08 23:58:51 +07:00
softsimon
0e8d04e464 Merge pull request #5049 from mempool/natsoni/fix-navbar
Fix transparent navbar
2024-05-08 23:30:33 +07:00
natsoni
fc76146085 Fix transparent navbar 2024-05-08 18:25:29 +02:00
softsimon
4142413002 Merge pull request #5028 from mempool/mononaut/x-widget
Add x widget
2024-05-08 22:57:21 +07:00
softsimon
49e4855982 Merge branch 'master' into mononaut/x-widget 2024-05-08 22:56:03 +07:00
dependabot[bot]
65cad2025a Bump cypress from 13.8.0 to 13.9.0 in /frontend
Bumps [cypress](https://github.com/cypress-io/cypress) from 13.8.0 to 13.9.0.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cypress-io/cypress/commits)

---
updated-dependencies:
- dependency-name: cypress
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-08 15:29:38 +00:00
softsimon
64327b3d25 Merge pull request #5047 from mempool/dependabot/npm_and_yarn/frontend/esbuild-0.21.1
Bump esbuild from 0.20.2 to 0.21.1 in /frontend
2024-05-08 22:28:27 +07:00
wiz
54289368b1 Merge pull request #5027 from mempool/mononaut/tracker-logos
Add enterprise logos to pizza tracker header
2024-05-08 17:49:27 +09:00
softsimon
297c226fab Merge branch 'master' into mononaut/tracker-logos 2024-05-08 10:06:47 +07:00
softsimon
7f5b5f487e Merge pull request #5044 from mempool/mononaut/onbtc-imgs-themed
onbtc theme
2024-05-08 10:06:13 +07:00
softsimon
f2a6828015 Merge pull request #5045 from mempool/mononaut/fix-custom-dash-graph
Fix custom dashboard address graph
2024-05-08 10:01:31 +07:00
dependabot[bot]
45936805e9 Bump esbuild from 0.20.2 to 0.21.1 in /frontend
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.20.2 to 0.21.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.20.2...v0.21.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-08 02:17:49 +00:00
Mononaut
0fae0b21ad Fix pizza tracker branding layout 2024-05-08 00:23:50 +00:00
Mononaut
9bd741b1d9 Add enterprise logos to pizza tracker header 2024-05-08 00:23:50 +00:00
Mononaut
c15393981d Update onbtc fallback preview img 2024-05-08 00:22:54 +00:00
Mononaut
9495d7d2ea Fix custom dashboard address graph 2024-05-08 00:17:35 +00:00
Mononaut
788a95b260 disable selector when forcing custom theme 2024-05-07 23:48:28 +00:00
Mononaut
4c697d0008 Update onbtc theme & branding 2024-05-07 23:48:23 +00:00
natsoni
463e66081c CSS theme changes 2024-05-07 21:03:26 +00:00
natsoni
a7e501570c Add El Salvador theme 2024-05-07 21:03:25 +00:00
natsoni
4a76cb083a More css included in color themes 2024-05-07 21:03:24 +00:00
natsoni
6a767ce5c4 Make links color manageable with themes 2024-05-07 21:02:40 +00:00
wiz
0fcd132df4 Merge pull request #5041 from mempool/mononaut/unbork-orphan-cache
Don't get stuck fetching orphans
2024-05-07 17:55:37 +09:00
Mononaut
0990cfe072 Don't get stuck fetching orphans 2024-05-07 01:41:41 +00:00
wiz
d92bf14b50 Merge pull request #5040 from mempool/mononaut/testnet4
test
2024-05-07 02:50:37 +09:00
Mononaut
de8462e81e testnet4 docs 2024-05-06 17:31:49 +00:00
Mononaut
15bd58c746 testnet4 beta tags 2024-05-06 17:31:16 +00:00
Mononaut
26da18810d Hide lightning tab on testnet4 2024-05-06 16:48:39 +00:00
wiz
f80c99576b ops: Update prod scripts for testnet4 2024-05-07 01:01:09 +09:00
Mononaut
1e5a55917c Add testnet4 frontend support 2024-05-06 15:55:49 +00:00
softsimon
375e43c191 Merge pull request #5038 from mempool/mononaut/improve-deferred-mempool-animations
Improve deferred mempool visualization updates
2024-05-06 22:40:12 +07:00
softsimon
feab4d2a51 Merge pull request #5035 from mempool/mononaut/accelerator-websocket
Replace acceleration API polling with websocket
2024-05-06 20:57:41 +07:00
softsimon
73f0cdde7d Merge pull request #5039 from mempool/dependabot/github_actions/dtolnay/rust-toolchain-d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a
Bump dtolnay/rust-toolchain from bb45937a053e097f8591208d8e74c90db1873d07 to d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a
2024-05-06 18:02:04 +07:00
softsimon
810c2cbde4 Merge pull request #5037 from mempool/nymkappa/node-socket
[lightning] apply #4991 at node creation as well
2024-05-06 17:59:40 +07:00
softsimon
a9ff9c3bc7 Merge pull request #5030 from TheBlueMatt/msrv-take-2
Restore MSRV to 1.63
2024-05-06 12:51:42 +07:00
dependabot[bot]
69faa1d493 Bump dtolnay/rust-toolchain
Bumps [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) from bb45937a053e097f8591208d8e74c90db1873d07 to d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a.
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](bb45937a05...d8352f6b1d)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 02:26:59 +00:00
Mononaut
a29b29300e Replace acceleration API polling with websocket 2024-05-04 17:23:20 +00:00
Mononaut
56a0d89b88 Improve deferred mempool visualization updates 2024-05-04 17:08:38 +00:00
nymkappa
2e49ef38c9 [lightning] apply 4991 at node creation as well 2024-05-04 10:01:21 +02:00
softsimon
8ec5dd70e0 Merge pull request #5026 from mempool/mononaut/balance-graph-span-toggle
Polish address balance graph, add period toggle
2024-05-04 14:31:26 +07:00
softsimon
89363bfbff Merge pull request #4815 from mempool/mononaut/testmempoolaccept
testmempoolaccept
2024-05-04 14:22:37 +07:00
softsimon
e45a03729b Adding footer link 2024-05-04 14:19:26 +07:00
softsimon
395ef82ad4 Merge branch 'master' into mononaut/testmempoolaccept 2024-05-04 14:11:48 +07:00
softsimon
02f1b11ad6 Merge pull request #5021 from mempool/dependabot/docker/docker/frontend/nginx-1.26.0-alpine
Bump nginx from 1.25.4-alpine to 1.26.0-alpine in /docker/frontend
2024-05-04 14:11:17 +07:00
softsimon
c53fb9266c Merge pull request #5023 from mempool/natsoni/block-view-fees-default
Change block display default mode to fees
2024-05-04 14:09:56 +07:00
softsimon
3d1edffd18 Merge pull request #5032 from mempool/mononaut/fix-mempool-animation-desync
Fix mempool animation desync
2024-05-04 14:08:42 +07:00
softsimon
24ea3199dd Merge pull request #4598 from mempool/mononaut/mempool-delta-subscription
Feature: track-mempool websocket subscriptions
2024-05-04 13:47:29 +07:00
Mononaut
44116424b0 Reimplement mempool animation smoothing within viz component 2024-05-03 21:43:53 +00:00
Mononaut
a8868b5f0f Simplify websocket mempool delta handling 2024-05-03 21:43:48 +00:00
Mononaut
5172f032e7 Add sequence number to track-mempool subscription messages 2024-05-03 16:31:56 +00:00
Mononaut
f8d30bf528 Add 'mempool delta' websocket subscriptions 2024-05-03 16:25:45 +00:00
softsimon
ba27ff9c42 Merge pull request #5022 from mempool/dependabot/npm_and_yarn/backend/ws-8.17.0
Bump ws from 8.16.0 to 8.17.0 in /backend
2024-05-03 05:16:37 +07:00
softsimon
02878d6a58 Merge pull request #5029 from mempool/dependabot/npm_and_yarn/unfurler/ejs-3.1.10
Bump ejs from 3.1.9 to 3.1.10 in /unfurler
2024-05-03 05:15:15 +07:00
Matt Corallo
a676d23a54 Restore MSRV to 1.63
In trying to upgrade my mempool instance, I discovered I couldn't
build the latest mempool Rust code with my available Rust
toolchain.

It appears in #4612 the Rust MSRV was bumped without justification,
which is reverted here. Note that `Cargo.lock` updates here should
ensure the versions of dependent crates use the versions supported
by our MSRV.

Its possible that the dependency downgrades here break something,
but things appear to be running fine for me locally, so figured I'd
suggest this upstream.
2024-05-02 21:00:49 +00:00
dependabot[bot]
36f1f778c3 Bump ejs from 3.1.9 to 3.1.10 in /unfurler
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-02 18:34:47 +00:00
Mononaut
f83404421a Add x widget 2024-05-01 23:59:02 +00:00
wiz
cd8abe5c06 ops: Tweak http port for onbtc unfurler instance 2024-05-02 00:53:14 +09:00
softsimon
471a2397c0 Add customize.js to .gitignore 2024-05-01 22:51:58 +07:00
Mononaut
a582a3b0ed Polish address balance graph, add period toggle 2024-04-29 21:03:09 +00:00
softsimon
20df70c449 Fix missing return on invalid params 2024-04-30 02:02:53 +07:00
wiz
af13b6c9f8 Merge pull request #5024 from mempool/mononaut/onbtc-siteid
Add onbtc site id
2024-04-30 01:33:36 +09:00
Mononaut
06ee82af0b Add onbtc site id 2024-04-29 14:36:45 +00:00
natsoni
57b466d80f Update block display default mode to fees 2024-04-29 11:54:28 +02:00
wiz
dbd4d152ce Merge pull request #5020 from mempool/mononaut/custom-unfurls
custom unfurls
2024-04-29 17:37:11 +09:00
wiz
95f47409d8 Add onbtc frontend config, add onbtc backend to build script 2024-04-29 17:29:45 +09:00
wiz
ed64b72676 ops: Add onbtc frontend config 2024-04-29 17:20:35 +09:00
wiz
f9948055e4 ops: Update build/unfurler stuff for onbtc 2024-04-29 17:12:51 +09:00
dependabot[bot]
6ad709f93a Bump ws from 8.16.0 to 8.17.0 in /backend
Bumps [ws](https://github.com/websockets/ws) from 8.16.0 to 8.17.0.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.16.0...8.17.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 02:16:33 +00:00
dependabot[bot]
f9498cf5a2 Bump nginx from 1.25.4-alpine to 1.26.0-alpine in /docker/frontend
Bumps nginx from 1.25.4-alpine to 1.26.0-alpine.

---
updated-dependencies:
- dependency-name: nginx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 02:06:21 +00:00
Mononaut
f5333c529c Update unfurler routing, add fallback img 2024-04-28 16:18:35 +00:00
Mononaut
6b2cc23cd3 Add custom branding to unfurler previews 2024-04-28 16:18:16 +00:00
Mononaut
34329d04f0 favicons 2024-04-28 16:02:42 +00:00
Mononaut
701c283e97 Fix svg 2024-04-28 16:02:41 +00:00
Mononaut
f9fd589af2 Logo overrides, custom index.html 2024-04-28 16:02:41 +00:00
softsimon
0d9602693b Correcting duplicate string 'Date' 2024-04-28 09:46:55 +07:00
softsimon
27be568eb5 Fix duplicate "sats" string 2024-04-28 09:43:35 +07:00
softsimon
8f06aea736 Extracting new i18n strings 2024-04-28 09:39:04 +07:00
wiz
65191538e2 Merge pull request #5016 from mempool/mononaut/customization
Customized frontend builds
2024-04-28 04:11:11 +09:00
Mononaut
d49485c363 Update custom address widgets on new block transactions 2024-04-26 23:59:27 +00:00
Mononaut
1de028840e Custom address widget title links 2024-04-26 20:06:34 +00:00
Mononaut
3f9fb68c5f Fix mobile custom enterprise logo 2024-04-26 20:01:00 +00:00
Mononaut
33ef15511f Check in missing address transactions widget component 2024-04-26 19:41:11 +00:00
Mononaut
139c384e97 Add address transactions widget 2024-04-26 19:17:28 +00:00
Mononaut
ccb27dbdb9 Address widget timespans 2024-04-26 19:17:27 +00:00
Mononaut
6eb21ffd24 check in svg 2024-04-26 19:17:27 +00:00
Mononaut
b9b3c8f78a Custom branding 2024-04-26 19:17:27 +00:00
Mononaut
4f4215577a Add custom dashboard component 2024-04-26 19:17:27 +00:00
Mononaut
ac0f56325b Add customizable enterprise build 2024-04-26 19:17:27 +00:00
softsimon
fd8df2a035 Merge pull request #5015 from mempool/natsoni/add-toggle-sats
Add an option to display values as sats
2024-04-26 12:58:52 +07:00
softsimon
b4decb8f91 Merge pull request #5014 from mempool/natsoni/small-fixes-liquid-frontend
Minor changes on Liquid frontend
2024-04-26 12:07:07 +07:00
softsimon
836c1e002e Merge pull request #4981 from mempool/natsoni/fix-tx-details-loading
Fix miner data loading in transaction details
2024-04-26 12:06:03 +07:00
softsimon
701701e13c Merge pull request #5013 from mempool/natsoni/fix-liquid-footer
Remove Mining dashboard from Liquid footer
2024-04-26 11:54:36 +07:00
natsoni
ea5c3212d6 Add an option to display values as sats 2024-04-24 16:44:56 +02:00
natsoni
65705a8a1d Fix Liquid UTXOs table color 2024-04-24 14:19:14 +02:00
natsoni
8310ad9f24 Fix gauge clickable pointer 2024-04-24 14:17:50 +02:00
natsoni
1a5c2c4d3a Add a 5% tolerance for declaring a "unpeg event" 2024-04-24 14:17:40 +02:00
natsoni
cd689afaaa Add config check to show mining dashboard in footer 2024-04-24 11:00:51 +02:00
softsimon
00e0eea60e Merge pull request #5012 from mempool/dependabot/npm_and_yarn/backend/mysql2-3.9.7
Bump mysql2 from 3.9.4 to 3.9.7 in /backend
2024-04-24 10:38:42 +07:00
softsimon
d89ba767f1 Merge pull request #5011 from mempool/mononaut/rbf-mobile-autoscroll-bug
Fix rbf autoscroll bug on mobile
2024-04-24 10:34:34 +07:00
dependabot[bot]
c4acc95b06 Bump mysql2 from 3.9.4 to 3.9.7 in /backend
Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.9.4 to 3.9.7.
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](https://github.com/sidorares/node-mysql2/compare/v3.9.4...v3.9.7)

---
updated-dependencies:
- dependency-name: mysql2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-23 23:11:18 +00:00
nymkappa
ed9f792d21 fix typo 2024-04-23 17:54:04 +02:00
Mononaut
31c008afa5 Fix rbf autoscroll bug on mobile 2024-04-22 21:52:48 +00:00
softsimon
a66920c51e Merge pull request #5004 from mempool/natsoni/toggle-block-fees
Add block display mode toggle
2024-04-22 20:30:26 +07:00
softsimon
1c9e2b546b Replacing setTimeouts with rxjs 2024-04-22 14:42:48 +07:00
softsimon
2297ff72f5 Merge pull request #5006 from mempool/knorrium/increase_language_selector_width
Knorrium/increase language selector width
2024-04-22 10:24:38 +07:00
Felipe Knorr Kuhn
fbaa39a300 Change only the selector width 2024-04-21 19:34:48 -07:00
Felipe Knorr Kuhn
bd2c15c6bb Increase the language selector width to accomodate all languages 2024-04-21 18:48:44 -07:00
natsoni
a4d8f2db58 Add block display mode toggle button 2024-04-21 14:54:50 +02:00
Mononaut
2a43255802 testmempool accept more validation & switch to JSON array format 2024-04-21 10:05:58 +07:00
Mononaut
f3232b2d5c Add Test Transactions page 2024-04-21 10:05:58 +07:00
Mononaut
c7cb7d1ac4 Add testmempoolaccept API endpoint 2024-04-21 10:05:57 +07:00
softsimon
bd5a23ff0d Merge pull request #4997 from mempool/natsoni/fix-dropdown-focus
Fix dropdown menu navigation with arrow keys
2024-04-20 08:35:17 +07:00
natsoni
dd0c1eb74d Fix dropdown menu arrow navigation 2024-04-20 00:57:59 +02:00
softsimon
9950ef16ce Merge pull request #4995 from mempool/natsoni/fix-pagination-color
Fix table pagination colors on custom themes
2024-04-20 01:25:56 +07:00
softsimon
fed2c034ee Merge pull request #4996 from mempool/nymkappa/update-doc
[doc] update accel doc
2024-04-20 00:47:03 +07:00
nymkappa
4944e3c257 [doc] update accel doc 2024-04-19 18:45:59 +02:00
natsoni
b865fa33f6 Fix table pagination colors 2024-04-19 17:08:19 +02:00
softsimon
bb44ed15da Merge pull request #4991 from mempool/nymkappa/unique-node-addr
[lightning] unique sockets for ln nodes
2024-04-19 18:02:42 +07:00
softsimon
93919421ee Merge pull request #4984 from mempool/natsoni/more-css
More css fixes
2024-04-19 16:24:25 +07:00
softsimon
8d1831129b Merge pull request #4988 from mempool/dependabot/npm_and_yarn/frontend/cypress-13.8.0
Bump cypress from 13.7.0 to 13.8.0 in /frontend
2024-04-19 16:23:30 +07:00
softsimon
5e6e2c037e Merge pull request #4979 from mempool/natsoni/improve-tooltip-positioning
Fix cropped transaction tooltip in block overview on mobile
2024-04-19 16:23:10 +07:00
nymkappa
ddfceddc57 [lightning] unique sockets for ln nodes 2024-04-19 10:56:15 +02:00
softsimon
5b19ffd524 Pull from transifex 2024-04-19 14:53:32 +07:00
wiz
7422001ed5 Merge pull request #4990 from mempool/nymkappa/disable-topology-prod
[prod] disable LN TOPOLOGY_FOLDER by default
2024-04-19 16:27:16 +09:00
nymkappa
3b0ef48cdf [prod] disable LN TOPOLOGY_FOLDER by default 2024-04-19 09:06:10 +02:00
wiz
d13875e3e7 Merge pull request #4989 from mempool/nymkappa/update-doc
[doc] update
2024-04-19 16:06:03 +09:00
nymkappa
6ebc7fab47 [doc] update to match accel history endpoint actual response 2024-04-19 08:55:21 +02:00
dependabot[bot]
9d931c1d13 Bump cypress from 13.7.0 to 13.8.0 in /frontend
Bumps [cypress](https://github.com/cypress-io/cypress) from 13.7.0 to 13.8.0.
- [Release notes](https://github.com/cypress-io/cypress/releases)
- [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/cypress-io/cypress/compare/v13.7.0...v13.8.0)

---
updated-dependencies:
- dependency-name: cypress
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 02:15:31 +00:00
softsimon
f86b6ac3de Merge pull request #4985 from mempool/mononaut/fix-audit-bug
Fix audit blockIndex increment bug
2024-04-19 00:13:48 +07:00
natsoni
ce1124284e Adapt more css for color theme 2024-04-18 16:18:38 +02:00
Mononaut
233f19d23d Fix audit blockIndex increment bug 2024-04-18 13:27:33 +00:00
natsoni
89ef5fe33d Fix tooltip text overflow in some languages 2024-04-18 11:09:28 +02:00
softsimon
7cd4345264 Merge pull request #4956 from henrialb/typo
Fix typo in docs
2024-04-18 11:59:57 +07:00
natsoni
bbaa5dafb9 Separate mining and audit subscriptions 2024-04-17 16:41:30 +02:00
wiz
cee1b39640 Merge pull request #4980 from mempool/mononaut/fix-acc-button-conditions 2024-04-17 04:06:27 +09:00
Mononaut
e388410e34 Re-add accelerate button on deep mempool txs 2024-04-16 18:52:13 +00:00
natsoni
c2d649d3f4 Improve tooltip position in block overview on mobile 2024-04-16 16:02:39 +02:00
wiz
d9dec44c8f Merge pull request #4977 from mempool/nymkappa/fix-copy
[accelerator] fix pizza accel copy/eta
2024-04-16 21:14:56 +09:00
wiz
c94a3a98eb Change "Settlement" to "Confirmation" 2024-04-16 21:14:45 +09:00
nymkappa
ec9b8359df [accelerator] fix pizza accel copy/eta x2 2024-04-16 21:12:25 +09:00
nymkappa
3db89e3dee [accelerator] fix pizza accel copy/eta 2024-04-16 21:09:30 +09:00
wiz
7f8dc74146 Merge pull request #4975 from mempool/nymkappa/mini-branch
[accelerator] polish pizza accel
2024-04-16 17:11:55 +09:00
wiz
83cea33727 Merge pull request #4969 from mempool/nymkappa/missing-accelerate-button-mobile
[accelerator] re-add missing accelerator button on mobile, set locati…
2024-04-16 17:02:25 +09:00
nymkappa
ada18a0413 [accelerator] fix redirects 2024-04-16 17:02:10 +09:00
nymkappa
96d85dcacd [accelerator] polish pizza accel x2 2024-04-16 16:56:37 +09:00
nymkappa
64ba033602 [accelerator] polish pizza accel 2024-04-16 16:01:52 +09:00
nymkappa
26807e80db Merge branch 'nymkappa/accel-summary-default' into nymkappa/mini-branch 2024-04-16 14:43:01 +09:00
nymkappa
e4f00285b3 Merge branch 'nymkappa/polish-accel' into nymkappa/mini-branch 2024-04-16 14:42:53 +09:00
nymkappa
d443e51d30 [accelerator] if eligible, show pizza accel summary by default 2024-04-16 11:45:04 +09:00
softsimon
b4352f5f25 Merge pull request #4972 from mempool/dependabot/github_actions/dtolnay/rust-toolchain-bb45937a053e097f8591208d8e74c90db1873d07
Bump dtolnay/rust-toolchain from dc6353516c68da0f06325f42ad880f76a5e77ec9 to bb45937a053e097f8591208d8e74c90db1873d07
2024-04-15 18:10:12 +07:00
softsimon
2c1b4a2547 Merge pull request #4955 from mempool/dependabot/npm_and_yarn/backend/mysql2-3.9.4
Bump mysql2 from 3.9.1 to 3.9.4 in /backend
2024-04-15 18:09:56 +07:00
softsimon
b54685bed7 Merge pull request #4973 from mempool/natsoni/fix-dropdown-menu-color
Adapt dropdown menu color to theme
2024-04-15 18:02:54 +07:00
natsoni
9032d5ac11 Adapt dropdown menu color to theme 2024-04-15 12:35:15 +02:00
dependabot[bot]
942747a0be Bump dtolnay/rust-toolchain
Bumps [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) from dc6353516c68da0f06325f42ad880f76a5e77ec9 to bb45937a053e097f8591208d8e74c90db1873d07.
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](dc6353516c...bb45937a05)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:00:06 +00:00
nymkappa
fdd6463ac0 [ui] polish pizza accelerator 2024-04-14 16:29:56 +09:00
nymkappa
e9afc4ec0f remove duplicated condition 2024-04-14 13:55:49 +09:00
nymkappa
dff811f615 [accelerator] re-add missing accelerator button on mobile, set location.hash when click on accelerate button 2024-04-14 13:54:25 +09:00
wiz
d1e382ddf7 Merge pull request #4967 from mempool/mononaut/fix-acc-fee-rate-again
Fix accelerated fee rate again
2024-04-14 13:20:06 +09:00
wiz
ccd460cf70 Merge pull request #4968 from mempool/mononaut/54-year-pizza
Fix first seen loading time in tx tracker
2024-04-14 13:18:56 +09:00
Mononaut
7b1ba912be Fix first seen loading time in tx tracker 2024-04-14 03:49:26 +00:00
Mononaut
f3864c9100 Fix accelerated fee rate again 2024-04-14 03:21:24 +00:00
wiz
1082889b64 Merge pull request #4966 from mempool/nymkappa/fix-fee-paid
[accelerator] fix fee paid calculation
2024-04-14 00:20:42 +09:00
nymkappa
36839fdcb9 [accelerator] fix fee paid calculation 2024-04-14 00:19:14 +09:00
wiz
ebe54d47d8 Merge pull request #4965 from mempool/nymkappa/remove-prepaid-tx
[accelerator] remove simple mode from tx page
2024-04-14 00:15:02 +09:00
nymkappa
7219823847 [accelerator] remove simple mode from tx page 2024-04-14 00:10:15 +09:00
wiz
336fc7a64a Merge pull request #4964 from mempool/mononaut/bitcoin-tracker
Add Bitcoin logo to tracker header
2024-04-13 23:20:46 +09:00
Mononaut
84f62f8025 Add Bitcoin logo to tracker page 2024-04-13 14:18:01 +00:00
wiz
6bd6dfec49 Merge pull request #4963 from mempool/nymkappa/accel-checkout
[accelerator] remove test code
2024-04-13 23:13:29 +09:00
nymkappa
a6e27f1312 Merge branch 'master' into nymkappa/accel-checkout 2024-04-13 23:13:02 +09:00
nymkappa
c913df837a [accelerator] remove test code 2024-04-13 23:12:05 +09:00
wiz
42bd9811e3 Merge pull request #4962 from mempool/nymkappa/accel-checkout
[accelerator] polish prepaid accel UI
2024-04-13 23:09:03 +09:00
wiz
7fbf13fd9d Merge pull request #4961 from mempool/mononaut/pizza-layout
Change tx tracker layout
2024-04-13 23:08:10 +09:00
nymkappa
fb086b5ad5 [accelerator] polish UI prepaid accel 2024-04-13 23:07:19 +09:00
Mononaut
d4f51979d4 Change tx tracker layout 2024-04-13 13:56:29 +00:00
wiz
aeb54f59f1 Merge pull request #4960 from mempool/mononaut/change-toppings
change pizza tracker toppings
2024-04-13 22:17:10 +09:00
Mononaut
5ca3b24527 Fix accelerate arrow, add accelerating status 2024-04-13 13:14:26 +00:00
Mononaut
88df2878cb Add logo to tx tracker page, fix bugs 2024-04-13 12:26:30 +00:00
nymkappa
f601bbc499 Merge branch 'master' into nymkappa/accel-checkout 2024-04-13 21:13:11 +09:00
nymkappa
24e9ae6440 [accelerator] re-integrate square payment WIP 2024-04-13 20:53:19 +09:00
wiz
8c3f11622c Merge pull request #4959 from mempool/mononaut/pizza
WIP (work-in-pizza)
2024-04-13 19:02:22 +09:00
Mononaut
da25ed431f hijack /tx/ page for tracker users 2024-04-13 09:24:52 +00:00
Mononaut
ecc234a96a Tracker bottom panel with status icon 2024-04-13 09:09:14 +00:00
Mononaut
29851537eb Prototype accelerate checkout properties 2024-04-13 09:09:13 +00:00
nymkappa
5f2d7b32ae [accelerator] concept accelerator a/b ctas 2024-04-13 09:09:13 +00:00
Mononaut
de00d49d7b Basic tracker page structure 2024-04-13 09:09:09 +00:00
wiz
bafa0f50fc Merge pull request #4958 from mempool/wiz/20240413-add-new-lightning-nodes
Add new VA1/FRA lightning nodes
2024-04-13 17:56:13 +09:00
wiz
80c75f9aeb Add new FRA lightning nodes 2024-04-13 17:49:29 +09:00
wiz
407eba194d Add new VA1 lightning nodes 2024-04-13 17:37:54 +09:00
Mononaut
2eac3e6555 Add pizza tracker component 2024-04-13 08:05:31 +00:00
Mononaut
61a308cbc6 Add simplified tracker blockchain component 2024-04-13 08:04:58 +00:00
nymkappa
49b9a6f53d [accelerator] concept accelerator a/b ctas 2024-04-13 16:11:49 +09:00
wiz
db1cc0d0e1 Merge pull request #4953 from mempool/nymkappa/use-bid-boost-accel-list
[accelerator] use new bidBoost field
2024-04-13 14:59:33 +09:00
Henrique Albuquerque
f89bd5b972 Add contributor statement 2024-04-12 18:05:18 +01:00
Henrique Albuquerque
d94a8cb58c Fix typo 2024-04-12 17:59:31 +01:00
dependabot[bot]
faf79e85fd Bump mysql2 from 3.9.1 to 3.9.4 in /backend
Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.9.1 to 3.9.4.
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](https://github.com/sidorares/node-mysql2/compare/v3.9.1...v3.9.4)

---
updated-dependencies:
- dependency-name: mysql2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-12 16:14:46 +00:00
softsimon
0d72e88c6a Updated from transifex 2024-04-12 16:18:39 +09:00
Mononaut
786ec7fff1 Mobile transaction tracker page 2024-04-12 07:16:01 +00:00
softsimon
8df497e53d Merge pull request #4954 from mempool/simon/convert-paid-currency-api
Convert v3 Currency api into v1
2024-04-12 16:07:50 +09:00
softsimon
1dd7a6ebac Convert v3 api into v1 2024-04-12 15:59:33 +09:00
nymkappa
2c12e9f64b [accelerator] use new bidBoost field 2024-04-12 15:12:25 +09:00
softsimon
9a77135d30 Merge pull request #4952 from mempool/simon/paid-currency-api
Paid currency api support
2024-04-12 14:42:56 +09:00
softsimon
f3b3b9b3f0 Adding new config to sample 2024-04-12 14:15:50 +09:00
softsimon
55c4d4d03d Paid currency api support 2024-04-12 14:10:47 +09:00
softsimon
061d341d8b Merge pull request #4949 from mempool/mononaut/delta-sequence
Add sequence number to mempool block updates
2024-04-12 00:30:30 +09:00
softsimon
1c29b8b260 Merge pull request #4950 from mempool/natsoni/fix-timestamp-datetime
Update prices table  'time' field to datetime
2024-04-11 21:07:26 +09:00
natsoni
79bfe9c866 Change time field to datetime 2024-04-11 17:56:34 +09:00
Mononaut
3e6d38656d Add sequence number to mempool block updates 2024-04-11 08:11:45 +00:00
softsimon
5697486cea Merge pull request #4947 from mempool/mononaut/hide-accelerate-panel-already
Hide accelerate panel if not needed
2024-04-11 15:54:55 +09:00
softsimon
df0da605e4 Merge pull request #4948 from mempool/mononaut/54-years
it hasn't been 54 years since this transaction was submitted
2024-04-11 15:54:39 +09:00
Mononaut
fa9aaf0423 Keep firstSeen loader, retry on fail 2024-04-11 06:03:14 +00:00
Mononaut
89de288fec it hasn't been 54 years since this transaction was submitted 2024-04-11 04:02:25 +00:00
Mononaut
940003552b Hide acceleration panel if tx is already accelerated 2024-04-10 11:14:13 +00:00
Mononaut
0660296b51 Disable accelerate button on successful submission 2024-04-10 11:14:13 +00:00
softsimon
affeb0a169 Merge pull request #4943 from mempool/nymkappa/fix-missing-qr-code
[accelerator] fix missing qr code
2024-04-10 19:15:04 +09:00
softsimon
2504653426 Merge pull request #4207 from mempool/mononaut/sipper
Proof-of-concept "sipper" lightweight pages for web crawlers
2024-04-10 18:29:42 +09:00
softsimon
b6a9ad67d3 Merge pull request #4946 from mempool/mononaut/acc-dash-vbytes
Switch success rate to total vsize
2024-04-10 18:12:48 +09:00
Mononaut
3d4741eac2 Fix acceleration skeleton loader titles 2024-04-10 09:03:31 +00:00
softsimon
5611f57e9e Merge pull request #4945 from mempool/mononaut/acc-dash-times
Add timespan toggle to acceleration dashboard
2024-04-10 18:00:25 +09:00
softsimon
44027f5bc0 Pull from transifex 2024-04-10 17:02:35 +09:00
Mononaut
41b25a78e9 adjust accelerator dashboard mobile fields 2024-04-10 07:22:02 +00:00
Mononaut
e263f94765 Switch success rate for total vsize on acceleration dashboard 2024-04-10 06:52:54 +00:00
Mononaut
80c7754e48 Add timespan toggle to acceleration dashboard 2024-04-10 06:27:48 +00:00
softsimon
165340324c Merge pull request #4944 from mempool/natsoni/more-css-fix
More css fixes
2024-04-10 15:15:47 +09:00
softsimon
735dddd604 Merge pull request #4941 from mempool/natsoni/polish-footer
Polish global footer
2024-04-10 15:03:13 +09:00
natsoni
3e3bd32705 Convert more css to variables 2024-04-10 14:41:19 +09:00
softsimon
f83025a9ff Merge pull request #4939 from mempool/hunicus/natsoni-formatting
Make room for natsoni as project member
2024-04-10 14:40:10 +09:00
softsimon
ba6a7b58aa Merge pull request #4942 from mempool/natsoni/fix-header-color-theme
Fix header color theme
2024-04-10 14:37:56 +09:00
nymkappa
fb8808ea59 [accelerator] fix redirect url, fix UX 2024-04-10 14:33:33 +09:00
nymkappa
73f241e9c3 Revert "Disable accelerate button after submission"
This reverts commit e55b1740db.
2024-04-10 13:53:21 +09:00
softsimon
317b1b6ac5 Merge pull request #4909 from mempool/nymkappa/unix-socket
[server] express server also listens on unix socket
2024-04-10 13:41:14 +09:00
natsoni
cabe629f17 Add icon color variable to color themes 2024-04-10 12:26:00 +09:00
natsoni
9f6521b987 Update navbar background color 2024-04-10 12:25:30 +09:00
hunicus
6daffe5b13 Make room for natsoni as project member 2024-04-10 11:39:25 +09:00
nymkappa
4c7f93d1ef fix tests 2024-04-10 09:46:05 +09:00
natsoni
f81bbb93a5 Polish global footer 2024-04-09 20:55:14 +09:00
wiz
b0058e94ce Merge pull request #4938 from mempool/nymkappa/accel-graph
[accelerator] show 1w accel graph by default
2024-04-09 20:17:51 +09:00
nymkappa
de069f704a [accelerator] show 1w accel graph by default 2024-04-09 18:10:53 +09:00
wiz
bcb8493cd0 Merge pull request #4937 from mempool/mononaut/disable-acc-button
disable accelerate button after submission
2024-04-09 18:07:30 +09:00
Mononaut
e55b1740db Disable accelerate button after submission 2024-04-09 08:31:24 +00:00
softsimon
df673b2a4e Pull from transifex 2024-04-09 16:58:02 +09:00
wiz
a4753769d2 Merge pull request #4936 from mempool/natsoni/add-wiz-theme
Add wiz theme
2024-04-09 16:50:31 +09:00
wiz
be75a87e88 Merge pull request #4935 from mempool/mononaut/acc-rate-tooltips
Fix accelerated fee rate in mined block tooltips
2024-04-09 16:50:21 +09:00
nymkappa
0ac3ae1cb1 Update docker/backend/start.sh
Co-authored-by: softsimon <softsimon@users.noreply.github.com>
2024-04-09 16:50:04 +09:00
wiz
a5e1e95534 Fix capitalization of "wiz" theme 2024-04-09 16:49:41 +09:00
natsoni
7f6ab0b854 Add wiz theme 2024-04-09 16:43:02 +09:00
Mononaut
8420ecd380 Fix accelerated fee rate in mined block tooltips 2024-04-09 07:22:24 +00:00
softsimon
192fd09a00 Merge pull request #4933 from mempool/natsoni/fix-address-component-subscriptions
Fix subscription management in address component
2024-04-09 16:18:57 +09:00
softsimon
d9a59c6d1e Accelerator i18n fixes 2024-04-09 16:15:26 +09:00
softsimon
69c3c3162c Theme selector i18n fix 2024-04-09 16:08:58 +09:00
softsimon
61ba832dfd Merge pull request #4932 from mempool/natsoni/remove-console-log
Remove console log
2024-04-09 15:17:34 +09:00
natsoni
f332bba468 Remove console log 2024-04-09 15:14:50 +09:00
nymkappa
ba8cca6ba5 Merge branch 'master' into nymkappa/unix-socket 2024-04-09 15:11:52 +09:00
nymkappa
7a098952c8 [server] disable unix socket listening by default 2024-04-09 15:11:40 +09:00
softsimon
347b74a55d Theme elector width fix 2024-04-09 14:51:54 +09:00
wiz
d5b0adeeed Merge pull request #4931 from mempool/nymkappa/additional-error-message
add additional error message
2024-04-09 14:50:05 +09:00
softsimon
d8c4d36d4b Rename themes 2024-04-09 14:49:24 +09:00
nymkappa
aac32c5bff add additional error message 2024-04-09 14:33:18 +09:00
softsimon
ff86e55622 Merge pull request #4916 from mempool/natsoni/high-contrast-theme
High contrast theme (duplicate)
2024-04-09 14:33:09 +09:00
wiz
894c4cb139 Merge pull request #4930 from mempool/mononaut/filter-acc
Filter accelerations for matching pool
2024-04-09 14:32:27 +09:00
softsimon
2792016383 Fix relative imports 2024-04-09 14:24:34 +09:00
wiz
46215871aa Merge pull request #4928 from mempool/hunicus/bid-boost-link
Make bid boost widget link clickable on mobile
2024-04-09 14:24:28 +09:00
natsoni
cfc06be975 Fix subscription management in address component 2024-04-09 14:21:08 +09:00
natsoni
527589ac04 Merge branch 'master' into natsoni/high-contrast-theme 2024-04-09 12:13:11 +09:00
Mononaut
43845cda5c Filter accelerations for matching pool 2024-04-09 00:08:50 +00:00
wiz
b74b8a8a5a Merge pull request #4929 from mempool/mononaut/fix-acc-rate-labels
Fix accelerated rate labels
2024-04-08 23:13:20 +09:00
Mononaut
226c6d8432 More acceleration labelling fixes 2024-04-08 14:08:11 +00:00
Mononaut
9f79258dec Fix accelerated/effective rate labelling 2024-04-08 13:45:05 +00:00
Mononaut
13bcc99095 Fix block summary data fields 2024-04-08 13:44:08 +00:00
hunicus
fef9b93a05 Merge branch 'master' into hunicus/bid-boost-link 2024-04-08 22:17:10 +09:00
hunicus
ccac3437cf Make bid boost widget link clickable on mobile 2024-04-08 22:09:52 +09:00
wiz
e849a31668 Merge pull request #4926 from mempool/nymkappa/duplicated-accel
[accelerator] fix possible duplicated accel request call
2024-04-08 21:57:17 +09:00
nymkappa
88de5412f8 [accelerator] reset cashapp upon price update 2024-04-08 21:55:47 +09:00
wiz
d13c48d31d Merge pull request #4925 from mempool/simon/fix-first-seen-skeleton
Fix first seen skeleton loader
2024-04-08 21:55:31 +09:00
softsimon
4c807866a3 Fix first seen skeleton loader 2024-04-08 21:44:29 +09:00
nymkappa
d7a4a95c05 [accelerator] avoid duplicated accel request 2024-04-08 21:43:06 +09:00
wiz
b35422ff9f Merge pull request #4922 from mempool/mononaut/actually-fix-sticky-button
Actually fix sticky button
2024-04-08 21:32:28 +09:00
wiz
b6be5d71bb Merge pull request #4923 from mempool/mononaut/wider-sticky-button
Slightly wider sticky button
2024-04-08 21:32:12 +09:00
wiz
4c5eddcf6d Merge pull request #4924 from mempool/mononaut/fix-acc-fee-rate
Fix missing accelerated fee rate again
2024-04-08 21:31:27 +09:00
softsimon
51f0b75a64 Merge pull request #4917 from mempool/natsoni/fix-liquid-dropdown-position
Fix dropdown menu position in Liquid
2024-04-08 21:03:39 +09:00
softsimon
fe4648cd9e Update frontend/src/app/components/liquid-master-page/liquid-master-page.component.scss 2024-04-08 21:03:25 +09:00
softsimon
03867ada49 Merge pull request #4887 from mempool/mononaut/local-acceleration-data
Local acceleration data
2024-04-08 20:43:46 +09:00
Mononaut
7959188c06 Fix missing accelerated fee rate again 2024-04-08 11:36:22 +00:00
Mononaut
11eaa0ca50 Fix NaN boost 2024-04-08 11:22:57 +00:00
Mononaut
dc6dba416a Fix missing boost 2024-04-08 11:22:57 +00:00
Mononaut
c8e7cc773a Always use local data for pending/historical accelerations 2024-04-08 11:22:57 +00:00
Mononaut
efe43329a1 Support PREFER_LOCAL for /accelerations(/history) 2024-04-08 11:22:56 +00:00
Mononaut
3f97c17af2 Fetch block accelerations by height instead of hash 2024-04-08 11:22:56 +00:00
Mononaut
91493e8769 Roll back local acceleration data on reorg 2024-04-08 11:22:56 +00:00
softsimon
5f36cb88ab Merge pull request #4895 from mempool/hunicus/txacc-faq
Add note about accelerator waitlist to faq
2024-04-08 18:44:40 +09:00
Mononaut
a0ef635c92 Slightly wider sticky button 2024-04-08 09:36:45 +00:00
natsoni
d68904fec0 More contrast theme fixes 2024-04-08 18:32:59 +09:00
Mononaut
b679581cf2 Actually fix sticky button 2024-04-08 09:25:53 +00:00
softsimon
f7e6fa026d Merge pull request #4921 from mempool/mononaut/fix-accel-highlight
Fix new acceleration visualization color change
2024-04-08 18:04:23 +09:00
softsimon
96f16f1f2e Merge pull request #4914 from mempool/hunicus/why-no-confirm
Remove another faq reference to tx-acc
2024-04-08 18:01:34 +09:00
Mononaut
ad8fa8722f Fix new acceleration color change 2024-04-08 09:00:51 +00:00
wiz
e477f09cd5 Merge pull request #4920 from mempool/mononaut/fix-tx-autoscroll
Fix tx autoscroll
2024-04-08 17:59:54 +09:00
Mononaut
be5eb9ef70 Fix tx autoscroll 2024-04-08 08:51:35 +00:00
wiz
b952642570 Update staging URLs for prod Square SDK load 2024-04-08 17:12:38 +09:00
wiz
47cc74a351 Merge pull request #4913 from mempool/mononaut/simple-acceleration-mode
Simplified acceleration mode for mobile
2024-04-08 17:06:20 +09:00
wiz
cff572a104 Merge branch 'master' into mononaut/simple-acceleration-mode 2024-04-08 17:05:51 +09:00
Mononaut
48e16e64c2 Simple mode redesign w/ sticky button 2024-04-08 08:05:22 +00:00
wiz
46172836f1 Merge pull request #4915 from mempool/simon/fix-mobile-initial-zoom
Fix initial zoom behavior on mobile
2024-04-08 16:13:18 +09:00
wiz
eacb72a05b Merge pull request #4912 from mempool/mononaut/accelerated-fee-rates
Fix accelerated fee rates
2024-04-08 16:10:55 +09:00
softsimon
c1fefaab92 Pull from transifex 2024-04-08 16:01:26 +09:00
natsoni
f2f86457ee Fix dropdown menu position in Liquid 2024-04-08 15:53:51 +09:00
natsoni
c251b5831b Merge branch 'master' into natsoni/contrast-theme 2024-04-08 14:54:38 +09:00
softsimon
8c589d3000 Fix initial zoom behavior on mobile
fixes #4875
2024-04-08 14:54:33 +09:00
hunicus
ddc599f6b7 Remove another faq reference to tx-acc 2024-04-08 14:18:53 +09:00
hunicus
6822c3a04b Merge branch 'master' into hunicus/txacc-faq 2024-04-08 14:00:00 +09:00
Mononaut
aa0c70bd44 Simplified acceleration mode for mobile 2024-04-08 04:58:56 +00:00
hunicus
94f649b345 Add condition for officialmempoolspace 2024-04-08 13:50:48 +09:00
Mononaut
5e07e9dceb Fix accelerated fee rates 2024-04-08 03:03:26 +00:00
softsimon
91a8a8be34 Merge pull request #4911 from daweilv/master
fix(address): clicking on the Balance History chart within the /testnet/address/:id page would incorrectly navigate to /tx/:tx
2024-04-08 11:41:54 +09:00
natsoni
99e1890795 Fix footer to fit with theme selector 2024-04-08 10:50:59 +09:00
Metadavid
5583befbba Update daweilv.txt 2024-04-07 21:42:36 +08:00
Metadavid
c637055859 Update daweilv.txt 2024-04-07 21:20:42 +08:00
Metadavid
c3acfb8781 Update address-graph.component.ts
fix(address): Clicking on the Balance History chart within the /testnet/address/:id page under testnet now navigates to /testnet/tx/:tx instead of /tx/:tx.
2024-04-07 21:04:23 +08:00
Metadavid
fd073a7043 Add files via upload 2024-04-07 21:03:20 +08:00
nymkappa
bed00fbd41 [server] express server also listens on unix socket 2024-04-07 18:39:37 +09:00
softsimon
501b79fdce Italian and Spanish fineshed 2024-04-07 10:43:17 +09:00
softsimon
ed6af8f560 Merge pull request #4906 from mempool/mononaut/acceleration-harmony
Acceleration sound
2024-04-06 19:02:26 +09:00
Mononaut
32495736d4 such bright wow harmony 2024-04-06 09:58:07 +00:00
wiz
bff48b0a64 Merge pull request #4901 from mempool/nymkappa/clear-state
[accelerator] clear state after loading preview
2024-04-06 18:47:46 +09:00
softsimon
f6228240ba Transifex pull 2024-04-06 18:04:39 +09:00
softsimon
215c8a7ff4 Merge pull request #4902 from mempool/mononaut/disappearing-effective-rates
Fix disappearing effective/accelerated fee rates
2024-04-06 18:00:07 +09:00
softsimon
0f217bd753 Merge pull request #4903 from mempool/mononaut/bright-purple
Don't apply age tint to accelerated txs
2024-04-06 17:39:24 +09:00
natsoni
7e920f4bae Replace more hardcoded css 2024-04-06 17:14:53 +09:00
natsoni
cde3d878b1 Fix block overview graph on contrast theme 2024-04-06 15:59:42 +09:00
natsoni
621a6ea42d Fix rbf tree fade out 2024-04-06 15:59:36 +09:00
natsoni
cfc3615e64 Fix global footer selector css 2024-04-06 15:59:30 +09:00
natsoni
2f8d0d90cd Update theme-contrast.css 2024-04-06 15:59:26 +09:00
natsoni
c827953ca5 Fix remaining bugs from rebase 2024-04-06 15:59:07 +09:00
Mononaut
79dd263fb1 add high contrast theme 2024-04-06 15:56:53 +09:00
Mononaut
1ca05a029a add theme selector to main dashboard 2024-04-06 15:56:22 +09:00
Mononaut
4c205eb09d implement theme switching service 2024-04-06 15:55:47 +09:00
natsoni
ee92f6639a convert to CSS variables 2024-04-06 15:54:55 +09:00
Mononaut
7721f16f9f Don't apply age tint to accelerated txs 2024-04-06 03:44:58 +00:00
nymkappa
854222b8cc [accelerator] clear state after loading preview 2024-04-06 12:32:33 +09:00
Mononaut
0bc86541c6 Fix disappearing effective/accelerated fee rates 2024-04-06 03:22:56 +00:00
softsimon
c45111333d Fix space 2024-04-06 11:58:48 +09:00
softsimon
ba6fedc430 Merge pull request #4900 from mempool/nymkappa/fix-accel-preview-autoscroll
[accelerator] fix accel preview autoscroll
2024-04-05 21:06:57 +09:00
nymkappa
caa34fa8bb [accelerator] fix accel preview autoscroll 2024-04-05 20:59:15 +09:00
softsimon
fa7c0ab58e Pull from transifex 2024-04-05 20:46:37 +09:00
wiz
2543eb6861 Merge pull request #4899 from mempool/nymkappa/polish-button
[accelerator] fix label accelerate button
2024-04-05 20:45:27 +09:00
nymkappa
c4cf3363ee [accelerator] fix label accelerate button 2024-04-05 20:39:43 +09:00
softsimon
98d4efd509 Merge pull request #4889 from mempool/mononaut/non-esplora-inscription-goggles
Add support for inscription goggles for non-esplora backends
2024-04-05 18:28:07 +09:00
softsimon
a99e65ee48 Merge branch 'master' into mononaut/non-esplora-inscription-goggles 2024-04-05 18:28:01 +09:00
wiz
3fd4d24c93 Merge pull request #4897 from mempool/nymkappa/fix-preview-loading
[accelerator] fix spinner loading
2024-04-05 18:23:38 +09:00
nymkappa
55a564a5a8 [accelerator] fix spinner loading 2024-04-05 17:52:07 +09:00
softsimon
04f6490eeb Merge branch 'master' into mononaut/non-esplora-inscription-goggles 2024-04-05 17:48:55 +09:00
softsimon
b5026789d6 Merge pull request #4896 from mempool/nymkappa/fix-referrer
[accelator] fix referrer check
2024-04-05 17:38:47 +09:00
nymkappa
705e570cf5 [accelator] fix referrer check 2024-04-05 17:36:04 +09:00
softsimon
10a41fb0d1 Merge pull request #4804 from mempool/nymkappa/prepaid-acceleration
[accelerator] prepaid acceleration
2024-04-05 17:32:46 +09:00
hunicus
2a591646c3 Add note about accelerator waitlist to faq 2024-04-05 16:53:19 +09:00
softsimon
e55898b414 Merge pull request #4891 from mempool/mononaut/tx-details-stripes
Transaction details template refactor
2024-04-05 16:50:41 +09:00
Mononaut
2df476406d Transaction details template refactor 2024-04-05 07:46:48 +00:00
softsimon
4a05b35f2b Pulling from transifex 2024-04-05 16:46:23 +09:00
softsimon
3ad8e56c25 Accelerations i18n key fix 2024-04-05 16:30:26 +09:00
softsimon
c1704758fd Merge pull request #4893 from mempool/simon/build-target-es2020
Set build target to es2020
2024-04-05 16:26:38 +09:00
softsimon
fe580f2b2b Merge pull request #4894 from mempool/hunicus/esplora-docs
Switch blockstream/electrs for mempool/electrs in backend readme
2024-04-05 16:19:35 +09:00
hunicus
d82e482acc Update readme: blockstream/electrs for mempool/electrs 2024-04-05 15:59:16 +09:00
softsimon
9d6142dc79 Merge pull request #4892 from mempool/hunicus/memsplora
Switch blockstream/electrs for mempool/electrs in faq
2024-04-05 15:57:14 +09:00
softsimon
2dec83735b Set build target to es2020 2024-04-05 15:56:10 +09:00
softsimon
9f4204b815 Remove goggles i18n 2024-04-05 15:50:34 +09:00
hunicus
40f2c972d7 Update faq: blockstream/electrs for mempool/electrs 2024-04-05 15:50:15 +09:00
softsimon
eee2385d0d Merge pull request #4886 from mempool/nymkappa/mempool-goggle-tm
[copy] add missing trademark to Mempool Goggles
2024-04-05 14:48:43 +09:00
softsimon
492c652157 Merge pull request #4885 from mempool/natsoni/fix-side-nav-on-rtl
Fix side panel for rtl languages
2024-04-05 14:47:36 +09:00
softsimon
af8e060dc6 A few goggles i18n 2024-04-05 12:37:17 +09:00
softsimon
6a19b9058f Merge pull request #4888 from mempool/mononaut/tint
Goggles age tint by default
2024-04-05 12:36:24 +09:00
nymkappa
bcbd21b922 [acclerator] load square for prepaid acceleration 2024-04-05 11:16:48 +09:00
nymkappa
aac76d68b0 Merge branch 'master' into nymkappa/prepaid-acceleration 2024-04-05 11:14:16 +09:00
softsimon
25bb942e04 Fixing the other i18n 2024-04-04 21:55:14 +09:00
softsimon
8ac71f21d1 Updating i18n for accelerator preview 2024-04-04 21:48:16 +09:00
Mononaut
287559f028 Add support for inscription goggles for non-esplora backends 2024-04-04 12:32:03 +00:00
Mononaut
7fbb93cf41 Goggles age tint by default 2024-04-04 11:33:51 +00:00
nymkappa
7b76e93631 Update messages.ca.xlf 2024-04-04 18:26:38 +08:00
wiz
896c451c3e Merge pull request #4884 from mempool/nymkappa/about-page 2024-04-04 19:18:48 +09:00
natsoni
0874386001 Fix side panel for rtl languages 2024-04-04 19:00:04 +09:00
nymkappa
ce6808b3c4 [copy] add missing trademark to Mempool Goggles 2024-04-04 18:01:01 +09:00
softsimon
40fa1c5acb Some language string updates 2024-04-04 17:51:42 +09:00
nymkappa
7307990f6f [about page] enable sponsors on self hosted 2024-04-04 17:36:37 +09:00
softsimon
5104da500e Adding i18n to mempool goggles 2024-04-04 17:28:32 +09:00
softsimon
3a8c46bbed Merge pull request #4837 from mempool/mononaut/goggles-age-filter
Goggles age filter
2024-04-04 16:47:34 +09:00
Mononaut
fc5312549d Switch gradient toggle fee -> default 2024-04-04 07:42:31 +00:00
softsimon
c14e8797e2 Merge pull request #4879 from mempool/nymkappa/proxy-accel-endpoints
[accelerator] proxy acceleration api to prod
2024-04-04 16:20:27 +09:00
nymkappa
0f26940018 Merge branch 'master' into nymkappa/proxy-accel-endpoints 2024-04-04 16:01:17 +09:00
Mononaut
26227e2f3b New opacity-based age Goggles 2024-04-04 06:57:38 +00:00
Mononaut
dcf78fab06 Block visualization color-by-age mode 2024-04-04 06:57:37 +00:00
softsimon
0813592a6d Merge pull request #4882 from mempool/nymkappa/reset-pool-sha
[migration] reset mining pool sha to force refreshing
2024-04-04 15:20:04 +09:00
nymkappa
abdb27af3f [migration] reset mining pool sha to force refreshing 2024-04-04 14:27:50 +09:00
nymkappa
b421be3315 [accelerator] also forward headers 2024-04-04 14:04:12 +09:00
softsimon
b74fbee069 Merge pull request #4881 from mempool/nymkappa/proxy-sponsors
[about page] proxy community sponsors apis to prod, small refactor
2024-04-04 14:02:00 +09:00
nymkappa
dab9357b40 [about page] proxy community sponsors apis to prod, small refactor 2024-04-04 13:56:39 +09:00
softsimon
ccd89604c0 Merge pull request #4856 from mempool/hunicus/docs-links-alignment
Fix api-docs anchor link vertical alignment
2024-04-04 13:54:53 +09:00
Felipe Knorr Kuhn
cd135b7171 Merge branch 'master' into nymkappa/proxy-accel-endpoints 2024-04-04 13:14:54 +09:00
softsimon
7e16d550b0 Merge pull request #4880 from mempool/knorrium/enable_fiat_price_again
Enable fiat price conversion again
2024-04-04 13:14:24 +09:00
nymkappa
404079ef4e [accelerator] use config.MEMPOOL_SERVICES.API url 2024-04-04 13:10:32 +09:00
Felipe Knorr Kuhn
d13f78f046 Enable fiat price conversion again 2024-04-04 13:10:11 +09:00
nymkappa
60040c3914 [accelerator] proxy acceleration api to prod 2024-04-04 12:57:54 +09:00
softsimon
e408fbd8d6 Merge branch 'master' into hunicus/docs-links-alignment 2024-04-04 12:40:14 +09:00
softsimon
6931ecd468 Merge pull request #4878 from mempool/dependabot/npm_and_yarn/backend/multi-b37afcb6a9
Bump ws and @types/ws in /backend
2024-04-04 12:31:27 +09:00
dependabot[bot]
ed5d30ea5b Bump ws and @types/ws in /backend
Bumps [ws](https://github.com/websockets/ws) and [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws). These dependencies needed to be updated together.

Updates `ws` from 8.13.0 to 8.16.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.16.0)

Updates `@types/ws` from 8.5.5 to 8.5.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ws)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/ws"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-04 02:49:18 +00:00
wiz
ead7613579 Merge pull request #4877 from mempool/nymkappa/truncate-hashrate-difficulty
[migration] re-index hashrates and difficulty_adjustments
2024-04-04 11:05:16 +09:00
wiz
ca0613ec17 Merge pull request #4876 from mempool/knorrium/change_default_replication_settings
Update default settings for Docker
2024-04-04 11:05:04 +09:00
nymkappa
727208ff84 [migration] re-index hashrates and difficulty_adjustments 2024-04-04 10:57:54 +09:00
Felipe Knorr Kuhn
53c3de2af5 Update default settings for Docker 2024-04-04 10:49:32 +09:00
softsimon
3f50d57ed1 Fix test broke due to i18n key change 2024-04-03 21:44:48 +09:00
softsimon
55f9d0f875 Merge branch 'master' into hunicus/docs-links-alignment 2024-04-03 18:57:46 +09:00
softsimon
37cb9b0fe8 Merge pull request #4872 from mempool/natsoni/fix-fiat-selector-sorting
Sort currency by ticker in fiat selector
2024-04-03 18:57:24 +09:00
softsimon
a3b5f79094 Merge pull request #4871 from mempool/natsoni/fix-liquid-testnet
Fix Liquid testnet broken blocks
2024-04-03 18:55:55 +09:00
softsimon
1b1ffa7109 Transifex pull 04-03 2024-04-03 18:47:16 +09:00
softsimon
2b3021c8fe Merge pull request #4873 from mempool/simon/i18n-improvements-04-02
i18n corrections and merging
2024-04-03 18:18:29 +09:00
softsimon
0ccc47786d Merge pull request #4874 from mempool/knorrium/fix_docker_for_rust
Fix the Docker workflow for the new Rust steps
2024-04-03 18:17:06 +09:00
softsimon
24366b929e i18n corrections and merging 2024-04-03 18:13:42 +09:00
Felipe Knorr Kuhn
2cab2a7885 Read FD from the environment variables 2024-04-03 18:13:14 +09:00
Felipe Knorr Kuhn
4284038c4b Fix error when deleting the missing @napi-rs folder 2024-04-03 18:12:51 +09:00
Felipe Knorr Kuhn
425d158777 Update Dockerfile to work with the new Rust steps 2024-04-03 18:12:26 +09:00
Felipe Knorr Kuhn
1a11b1813f Add more build contexts for the backend and rust-gbt 2024-04-03 18:11:58 +09:00
wiz
85a86d4b06 ops: Add backend git hash check 2024-04-03 17:28:58 +09:00
natsoni
073578243c Fix fiat selector sorting 2024-04-03 16:34:56 +09:00
wiz
69578f8086 Merge pull request #4820 from mempool/nymkappa/polish-enterprise-logo
[enterprise] polish enterprise logo
2024-04-03 16:17:37 +09:00
wiz
ec92f83a58 Merge pull request #4865 from mempool/mononaut/public-acceleration-config
Public acceleration config
2024-04-03 16:16:36 +09:00
wiz
37c0adbbfa Merge pull request #4868 from mempool/natsoni/fix-global-footer
Fix footer position
2024-04-03 16:15:57 +09:00
natsoni
e9c40692a6 Fix network switch mechanism 2024-04-03 16:09:30 +09:00
softsimon
32383b0bc2 Merge pull request #4869 from mempool/knorrium/more_bisq_cleanup
Cleanup more bisq references
2024-04-03 14:01:53 +09:00
Felipe Knorr Kuhn
d2f912d15c Remove leftover Bisq references from the GH workflow 2024-04-03 12:31:41 +09:00
Felipe Knorr Kuhn
c35c08b17a Remove bisq references from the tests 2024-04-03 12:31:17 +09:00
natsoni
4efd0dda83 Push footer down when content is less than window height 2024-04-03 11:28:25 +09:00
wiz
e7f8bcf83b ops: Update check script for elements electrs hash 2024-04-02 22:24:00 +09:00
wiz
08fa6d55b5 Merge pull request #4832 from mempool/dependabot/npm_and_yarn/frontend/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /frontend
2024-04-02 22:03:05 +09:00
Felipe Knorr Kuhn
995a26b944 Merge branch 'master' into nymkappa/prepaid-acceleration 2024-04-02 21:53:01 +09:00
Felipe Knorr Kuhn
3a2aad645b Merge branch 'master' into mononaut/public-acceleration-config 2024-04-02 21:31:28 +09:00
Felipe Knorr Kuhn
2c655a08be Merge branch 'master' into dependabot/npm_and_yarn/frontend/express-4.19.2 2024-04-02 21:13:34 +09:00
softsimon
bd359fdd31 Merge pull request #4859 from mempool/nymkappa/accel-copy
[accelerator] deposit -> top up
2024-04-02 21:12:47 +09:00
Felipe Knorr Kuhn
13216687dc Merge branch 'master' into nymkappa/accel-copy 2024-04-02 21:03:41 +09:00
wiz
1bf4f9902e Merge pull request #4862 from mempool/knorrium/fix_liquid_tests
Re-enable and fix Liquid tests
2024-04-02 21:02:42 +09:00
Felipe Knorr Kuhn
9908ec01aa Merge branch 'master' into mononaut/public-acceleration-config 2024-04-02 20:53:01 +09:00
Felipe Knorr Kuhn
051533bb14 Merge branch 'master' into knorrium/fix_liquid_tests 2024-04-02 20:48:36 +09:00
Mononaut
3d900bdfe5 Enable PUBLIC_ACCELERATIONS in mainnet prod 2024-04-02 08:24:57 +00:00
Mononaut
b32cce1440 Restrict public acceleration APIs to mainnet 2024-04-02 08:23:38 +00:00
Mononaut
37aee77eb4 Add config for public acceleration data 2024-04-02 08:19:35 +00:00
softsimon
72b8ae2a55 Revert id change 2024-04-02 17:07:18 +09:00
Felipe Knorr Kuhn
3e3bed9aa4 Disable viewport tests for now 2024-04-02 16:54:02 +09:00
Felipe Knorr Kuhn
844a86f997 Merge branch 'master' into knorrium/fix_liquid_tests 2024-04-02 16:44:04 +09:00
Felipe Knorr Kuhn
468f17c76c Add a few more Liquid testnet tests 2024-04-02 16:18:52 +09:00
softsimon
68a7a89fd6 Merge pull request #4864 from mempool/simon/update-i18n-04-02
Simon/update i18n 04 02
2024-04-02 16:06:07 +09:00
softsimon
931551a17f New extract 2024-04-02 16:05:52 +09:00
softsimon
7b79384f27 Extracting i18n 2024-04-02 16:02:18 +09:00
softsimon
69568421f3 Merge pull request #4232 from mempool/hunicus/move-on-in-it
Fix mining dashboard meta description not showing
2024-04-02 15:58:07 +09:00
softsimon
7fab57193d Merge pull request #4863 from mempool/mononaut/fix-block-page
Fix block audit cache bug
2024-04-02 15:32:58 +09:00
Mononaut
49146e1402 Fix mainnet blocks loading bug 2024-04-02 06:29:43 +00:00
hunicus
f28abd68f4 Merge branch 'master' into hunicus/move-on-in-it 2024-04-02 15:26:11 +09:00
Felipe Knorr Kuhn
4bb1320563 Re-enable and fix Liquid tests 2024-04-02 15:14:02 +09:00
dependabot[bot]
0a848f25a4 Bump express from 4.18.2 to 4.19.2 in /frontend
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 06:07:47 +00:00
wiz
83dbae3a70 Merge pull request #4786 from mempool/simon/remove-bisq
Removing Bisq
2024-04-02 15:06:23 +09:00
wiz
ce057bad20 ops: Bump NodeJS to v20.12.0 in start script 2024-04-02 14:58:55 +09:00
wiz
8bec0cacdb ops: Remove bisq from start script 2024-04-02 14:58:21 +09:00
wiz
faa990a2c6 ops: Remove bisq from build script 2024-04-02 14:55:52 +09:00
wiz
872e4b4a17 Merge pull request #4855 from mempool/knorrium/fix_missing_assets
Run sync-assets-dev when building
2024-04-02 14:55:03 +09:00
softsimon
3e0c36e714 Merge pull request #4854 from mempool/natsoni/fix-network-errors
Fix frontend errors on block loading
2024-04-02 14:38:26 +09:00
softsimon
974eaeb02f Merge branch 'master' into natsoni/fix-network-errors 2024-04-02 14:31:32 +09:00
softsimon
f833904d7b Merge pull request #4858 from mempool/mononaut/better-audits
Betterer audits
2024-04-02 14:30:17 +09:00
Mononaut
96ba7d0656 Add draft sip tx page 2024-04-02 05:28:03 +00:00
Mononaut
6852319e4d Improve sip API error handling 2024-04-02 05:26:27 +00:00
Mononaut
8bc1eaebc0 Sip set /block canonical url 2024-04-02 05:26:27 +00:00
Mononaut
eeefaa6374 Proof-of-concept "sipper" minimal pages for web crawlers 2024-04-02 05:26:24 +00:00
nymkappa
1bba1cfeb1 [accelerator] deposit -> top up 2024-04-02 11:35:03 +08:00
hunicus
fb6aec0afe Merge branch 'master' into hunicus/docs-links-alignment 2024-04-01 19:06:18 +09:00
Felipe Knorr Kuhn
7063d9e113 Merge branch 'master' into knorrium/fix_missing_assets 2024-04-01 19:04:06 +09:00
Felipe Knorr Kuhn
0445c150a2 Run sync-assets-dev when building 2024-04-01 19:01:08 +09:00
wiz
cfb1e1d908 ops: Remove nginx config for bisq 2024-04-01 19:00:37 +09:00
natsoni
3f80dbc5ce Fix random console errors on block loading 2024-04-01 18:53:50 +09:00
hunicus
b8a48314c1 Fix api-docs anchor link vertical alignment 2024-04-01 18:53:38 +09:00
softsimon
0090d36514 Merge branch 'master' into simon/remove-bisq 2024-04-01 18:52:56 +09:00
nymkappa
f4b9301f55 [enterprise] polish enterprise logo 2024-03-26 11:36:20 +09:00
softsimon
60996a99f0 Removing Bisq 2024-03-23 18:03:49 +09:00
nymkappa
1eb52d8a35 [accelerator] fix redirection link 2024-03-21 19:08:48 +09:00
nymkappa
8fee195577 [accelerator] prepaid acceleration 2024-03-21 16:44:07 +09:00
hunicus
89d805b500 Merge branch 'master' into hunicus/move-on-in-it 2024-01-26 08:40:40 -05:00
hunicus
572f6bdbac Fix mining dashboard meta description not showing 2023-09-06 22:42:58 +09:00
hunicus
42a222338b Add missing meta descriptions 2023-09-06 22:42:33 +09:00
515 changed files with 99791 additions and 87489 deletions

View File

@@ -35,7 +35,7 @@ jobs:
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
# Latest version available on this commit is 1.71.1
# Commit date is Aug 3, 2023
uses: dtolnay/rust-toolchain@dc6353516c68da0f06325f42ad880f76a5e77ec9
uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a
with:
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
@@ -251,7 +251,6 @@ jobs:
strategy:
fail-fast: false
matrix:
# module: ["mempool", "liquid", "bisq"] Disabling bisq support for now
module: ["mempool", "liquid"]
include:
- module: "mempool"
@@ -263,9 +262,6 @@ jobs:
spec: |
cypress/e2e/liquid/liquid.spec.ts
cypress/e2e/liquidtestnet/liquidtestnet.spec.ts
# - module: "bisq"
# spec: |
# cypress/e2e/bisq/bisq.spec.ts
name: E2E tests for ${{ matrix.module }}
steps:

View File

@@ -101,5 +101,7 @@ jobs:
--platform linux/amd64,linux/arm64 \
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \
--tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \
--build-context rustgbt=./rust \
--build-context backend=./backend \
--output "type=registry" ./${{ matrix.service }}/ \
--build-arg commitHash=$SHORT_SHA

View File

@@ -103,7 +103,7 @@ In particular, make sure:
- the correct Bitcoin Core RPC credentials are specified in `CORE_RPC`
- the correct `BACKEND` is specified in `MEMPOOL`:
- "electrum" if you're using [romanz/electrs](https://github.com/romanz/electrs) or [cculianu/Fulcrum](https://github.com/cculianu/Fulcrum)
- "esplora" if you're using [Blockstream/electrs](https://github.com/Blockstream/electrs)
- "esplora" if you're using [mempool/electrs](https://github.com/mempool/electrs)
- "none" if you're not using any Electrum Server
### 6. Run Mempool Backend

View File

@@ -35,7 +35,8 @@
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
"ALLOW_UNREACHABLE": true,
"PRICE_UPDATES_PER_HOUR": 1,
"MAX_TRACKED_ADDRESSES": 100
"MAX_TRACKED_ADDRESSES": 100,
"UNIX_SOCKET_PATH": ""
},
"CORE_RPC": {
"HOST": "127.0.0.1",
@@ -97,10 +98,6 @@
"GEOLITE2_ASN": "/usr/local/share/GeoIP/GeoLite2-ASN.mmdb",
"GEOIP2_ISP": "/usr/local/share/GeoIP/GeoIP2-ISP.mmdb"
},
"BISQ": {
"ENABLED": false,
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
},
"LIGHTNING": {
"ENABLED": false,
"BACKEND": "lnd",
@@ -131,9 +128,7 @@
"MEMPOOL_API": "https://mempool.space/api/v1",
"MEMPOOL_ONION": "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/api/v1",
"LIQUID_API": "https://liquid.network/api/v1",
"LIQUID_ONION": "http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1",
"BISQ_URL": "https://bisq.markets/api",
"BISQ_ONION": "http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api"
"LIQUID_ONION": "http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1"
},
"REDIS": {
"ENABLED": false,
@@ -157,6 +152,7 @@
},
"FIAT_PRICE": {
"ENABLED": true,
"PAID": false,
"API_KEY": "your-api-key-from-freecurrencyapi.com"
}
}

View File

@@ -3,7 +3,7 @@ set -e
# Cleaning up inside the node_modules folder
cd package/node_modules
rm -r \
rm -rf \
typescript \
@typescript-eslint \
@napi-rs

View File

@@ -18,12 +18,12 @@
"crypto-js": "~4.2.0",
"express": "~4.19.2",
"maxmind": "~4.3.11",
"mysql2": "~3.9.1",
"mysql2": "~3.9.7",
"redis": "^4.6.6",
"rust-gbt": "file:./rust-gbt",
"socks-proxy-agent": "~7.0.0",
"typescript": "~4.9.3",
"ws": "~8.13.0"
"ws": "~8.17.0"
},
"devDependencies": {
"@babel/code-frame": "^7.18.6",
@@ -32,7 +32,7 @@
"@types/crypto-js": "^4.1.1",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.0",
"@types/ws": "~8.5.5",
"@types/ws": "~8.5.10",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"eslint": "^8.36.0",
@@ -1858,9 +1858,9 @@
"dev": true
},
"node_modules/@types/ws": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -6197,9 +6197,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mysql2": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz",
"integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==",
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz",
"integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==",
"dependencies": {
"denque": "^2.1.0",
"generate-function": "^2.3.1",
@@ -7690,9 +7690,9 @@
}
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"engines": {
"node": ">=10.0.0"
},
@@ -9198,9 +9198,9 @@
"dev": true
},
"@types/ws": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -12382,9 +12382,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"mysql2": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz",
"integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==",
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz",
"integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==",
"requires": {
"denque": "^2.1.0",
"generate-function": "^2.3.1",
@@ -13424,9 +13424,9 @@
}
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"requires": {}
},
"y18n": {

View File

@@ -47,12 +47,12 @@
"crypto-js": "~4.2.0",
"express": "~4.19.2",
"maxmind": "~4.3.11",
"mysql2": "~3.9.1",
"mysql2": "~3.9.7",
"rust-gbt": "file:./rust-gbt",
"redis": "^4.6.6",
"socks-proxy-agent": "~7.0.0",
"typescript": "~4.9.3",
"ws": "~8.13.0"
"ws": "~8.17.0"
},
"devDependencies": {
"@babel/code-frame": "^7.18.6",
@@ -61,7 +61,7 @@
"@types/crypto-js": "^4.1.1",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.0",
"@types/ws": "~8.5.5",
"@types/ws": "~8.5.10",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"eslint": "^8.36.0",

View File

@@ -7,6 +7,7 @@
"BLOCKS_SUMMARIES_INDEXING": true,
"GOGGLES_INDEXING": false,
"HTTP_PORT": 1,
"UNIX_SOCKET_PATH": "/mempool/socket/mempool-bitcoin-mainnet",
"SPAWN_CLUSTER_PROCS": 2,
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
"AUTOMATIC_BLOCK_REINDEXING": false,
@@ -93,10 +94,6 @@
"ENABLED": false,
"TX_PER_SECOND_SAMPLE_PERIOD": 20
},
"BISQ": {
"ENABLED": true,
"DATA_PATH": "__BISQ_DATA_PATH__"
},
"SOCKS5PROXY": {
"ENABLED": true,
"USE_ONION": true,
@@ -109,9 +106,7 @@
"MEMPOOL_API": "__EXTERNAL_DATA_SERVER_MEMPOOL_API__",
"MEMPOOL_ONION": "__EXTERNAL_DATA_SERVER_MEMPOOL_ONION__",
"LIQUID_API": "__EXTERNAL_DATA_SERVER_LIQUID_API__",
"LIQUID_ONION": "__EXTERNAL_DATA_SERVER_LIQUID_ONION__",
"BISQ_URL": "__EXTERNAL_DATA_SERVER_BISQ_URL__",
"BISQ_ONION": "__EXTERNAL_DATA_SERVER_BISQ_ONION__"
"LIQUID_ONION": "__EXTERNAL_DATA_SERVER_LIQUID_ONION__"
},
"LIGHTNING": {
"ENABLED": true,
@@ -149,6 +144,7 @@
},
"FIAT_PRICE": {
"ENABLED": true,
"PAID": false,
"API_KEY": "__MEMPOOL_CURRENCY_API_KEY__"
}
}

View File

@@ -20,6 +20,7 @@ describe('Mempool Backend Config', () => {
BLOCKS_SUMMARIES_INDEXING: false,
GOGGLES_INDEXING: false,
HTTP_PORT: 8999,
UNIX_SOCKET_PATH: '',
SPAWN_CLUSTER_PROCS: 0,
API_URL_PREFIX: '/api/v1/',
AUTOMATIC_BLOCK_REINDEXING: false,
@@ -107,8 +108,6 @@ describe('Mempool Backend Config', () => {
expect(config.STATISTICS).toStrictEqual({ ENABLED: true, TX_PER_SECOND_SAMPLE_PERIOD: 150 });
expect(config.BISQ).toStrictEqual({ ENABLED: false, DATA_PATH: '/bisq/statsnode-data/btc_mainnet/db' });
expect(config.SOCKS5PROXY).toStrictEqual({
ENABLED: false,
USE_ONION: true,
@@ -122,9 +121,7 @@ describe('Mempool Backend Config', () => {
MEMPOOL_API: 'https://mempool.space/api/v1',
MEMPOOL_ONION: 'http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/api/v1',
LIQUID_API: 'https://liquid.network/api/v1',
LIQUID_ONION: 'http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1',
BISQ_URL: 'https://bisq.markets/api',
BISQ_ONION: 'http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api'
LIQUID_ONION: 'http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1'
});
expect(config.MAXMIND).toStrictEqual({
@@ -154,6 +151,7 @@ describe('Mempool Backend Config', () => {
expect(config.FIAT_PRICE).toStrictEqual({
ENABLED: true,
PAID: false,
API_KEY: '',
});
});
@@ -182,8 +180,6 @@ describe('Mempool Backend Config', () => {
expect(config.STATISTICS).toStrictEqual(fixture.STATISTICS);
expect(config.BISQ).toStrictEqual(fixture.BISQ);
expect(config.SOCKS5PROXY).toStrictEqual(fixture.SOCKS5PROXY);
expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER);

View File

@@ -0,0 +1,87 @@
import { Application } from "express";
import config from "../config";
import axios from "axios";
import logger from "../logger";
class AboutRoutes {
public initRoutes(app: Application) {
app
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations`, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, {
responseType: 'stream', timeout: 10000
});
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors`, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, {
responseType: 'stream', timeout: 10000
});
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators`, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, {
responseType: 'stream', timeout: 10000
});
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'services/sponsors', async (req, res) => {
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
try {
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
logger.err(`Unable to fetch sponsors from ${url}. ${e}`, 'About Page');
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'services/account/images/:username', async (req, res) => {
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
try {
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
logger.err(`Unable to fetch sponsor profile image from ${url}. ${e}`, 'About Page');
res.status(500).end();
}
})
;
}
}
export default new AboutRoutes();

View File

@@ -0,0 +1,69 @@
import { Application, Request, Response } from 'express';
import config from '../../config';
import axios from 'axios';
import logger from '../../logger';
import mempool from '../mempool';
import AccelerationRepository from '../../repositories/AccelerationRepository';
class AccelerationRoutes {
private tag = 'Accelerator';
public initRoutes(app: Application): void {
app
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations', this.$getAcceleratorAccelerations.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history/aggregated', this.$getAcceleratorAccelerationsHistoryAggregated.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/stats', this.$getAcceleratorAccelerationsStats.bind(this))
;
}
private async $getAcceleratorAccelerations(req: Request, res: Response): Promise<void> {
const accelerations = mempool.getAccelerations();
res.status(200).send(Object.values(accelerations));
}
private async $getAcceleratorAccelerationsHistory(req: Request, res: Response): Promise<void> {
const history = await AccelerationRepository.$getAccelerationInfo(null, req.query.blockHeight ? parseInt(req.query.blockHeight as string, 10) : null);
res.status(200).send(history.map(accel => ({
txid: accel.txid,
added: accel.added,
status: 'completed',
effectiveFee: accel.effective_fee,
effectiveVsize: accel.effective_vsize,
boostRate: accel.boost_rate,
boostCost: accel.boost_cost,
blockHeight: accel.height,
pools: [accel.pool],
})));
}
private async $getAcceleratorAccelerationsHistoryAggregated(req: Request, res: Response): Promise<void> {
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
try {
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
for (const key in response.headers) {
res.setHeader(key, response.headers[key]);
}
response.data.pipe(res);
} catch (e) {
logger.err(`Unable to get aggregated acceleration history from ${url} in $getAcceleratorAccelerationsHistoryAggregated(), ${e}`, this.tag);
res.status(500).end();
}
}
private async $getAcceleratorAccelerationsStats(req: Request, res: Response): Promise<void> {
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
try {
const response = await axios.get(url, { responseType: 'stream', timeout: 10000 });
for (const key in response.headers) {
res.setHeader(key, response.headers[key]);
}
response.data.pipe(res);
} catch (e) {
logger.err(`Unable to get acceleration stats from ${url} in $getAcceleratorAccelerationsStats(), ${e}`, this.tag);
res.status(500).end();
}
}
}
export default new AccelerationRoutes();

View File

@@ -1,6 +1,6 @@
import logger from '../logger';
import { MempoolTransactionExtended } from '../mempool.interfaces';
import { IEsploraApi } from './bitcoin/esplora-api.interface';
import logger from '../../logger';
import { MempoolTransactionExtended } from '../../mempool.interfaces';
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
const BLOCK_WEIGHT_UNITS = 4_000_000;
const BLOCK_SIGOPS = 80_000;

View File

@@ -75,10 +75,6 @@ class Audit {
let failures = 0;
let blockIndex = 1;
while (projectedBlocks[blockIndex] && failures < 500) {
if (index >= projectedBlocks[blockIndex].transactionIds.length) {
index = 0;
blockIndex++;
}
const txid = projectedBlocks[blockIndex].transactionIds[index];
const tx = mempool[txid];
if (tx) {
@@ -102,6 +98,10 @@ class Audit {
logger.warn('projected transaction missing from mempool cache');
}
index++;
if (index >= projectedBlocks[blockIndex].transactionIds.length) {
index = 0;
blockIndex++;
}
}
// mark unexpected transactions in the mined block as 'added'

View File

@@ -1,381 +0,0 @@
import { Application, Request, Response } from 'express';
import config from '../../config';
import { RequiredSpec } from '../../mempool.interfaces';
import bisq from './bisq';
import { MarketsApiError } from './interfaces';
import marketsApi from './markets-api';
class BisqRoutes {
public initRoutes(app: Application) {
app
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/stats', this.getBisqStats)
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/tx/:txId', this.getBisqTransaction)
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/block/:hash', this.getBisqBlock)
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/tip/height', this.getBisqTip)
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/blocks/:index/:length', this.getBisqBlocks)
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/address/:address', this.getBisqAddress)
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/txs/:index/:length', this.getBisqTransactions)
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/currencies', this.getBisqMarketCurrencies.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/depth', this.getBisqMarketDepth.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/hloc', this.getBisqMarketHloc.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/markets', this.getBisqMarketMarkets.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/offers', this.getBisqMarketOffers.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/ticker', this.getBisqMarketTicker.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/trades', this.getBisqMarketTrades.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes', this.getBisqMarketVolumes.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'bisq/markets/volumes/7d', this.getBisqMarketVolumes7d.bind(this))
;
}
private getBisqStats(req: Request, res: Response) {
const result = bisq.getStats();
res.json(result);
}
private getBisqTip(req: Request, res: Response) {
const result = bisq.getLatestBlockHeight();
res.type('text/plain');
res.send(result.toString());
}
private getBisqTransaction(req: Request, res: Response) {
const result = bisq.getTransaction(req.params.txId);
if (result) {
res.json(result);
} else {
res.status(404).send('Bisq transaction not found');
}
}
private getBisqTransactions(req: Request, res: Response) {
const types: string[] = [];
req.query.types = req.query.types || [];
if (!Array.isArray(req.query.types)) {
res.status(500).send('Types is not an array');
return;
}
for (const _type in req.query.types) {
if (typeof req.query.types[_type] === 'string') {
types.push(req.query.types[_type].toString());
}
}
const index = parseInt(req.params.index, 10) || 0;
const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25;
const [transactions, count] = bisq.getTransactions(index, length, types);
res.header('X-Total-Count', count.toString());
res.json(transactions);
}
private getBisqBlock(req: Request, res: Response) {
const result = bisq.getBlock(req.params.hash);
if (result) {
res.json(result);
} else {
res.status(404).send('Bisq block not found');
}
}
private getBisqBlocks(req: Request, res: Response) {
const index = parseInt(req.params.index, 10) || 0;
const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25;
const [transactions, count] = bisq.getBlocks(index, length);
res.header('X-Total-Count', count.toString());
res.json(transactions);
}
private getBisqAddress(req: Request, res: Response) {
const result = bisq.getAddress(req.params.address.substr(1));
if (result) {
res.json(result);
} else {
res.status(404).send('Bisq address not found');
}
}
private getBisqMarketCurrencies(req: Request, res: Response) {
const constraints: RequiredSpec = {
'type': {
required: false,
types: ['crypto', 'fiat', 'all']
},
};
const p = this.parseRequestParameters(req.query, constraints);
if (p.error) {
res.status(400).json(this.getBisqMarketErrorResponse(p.error));
return;
}
const result = marketsApi.getCurrencies(p.type);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketCurrencies error'));
}
}
private getBisqMarketDepth(req: Request, res: Response) {
const constraints: RequiredSpec = {
'market': {
required: true,
types: ['@string']
},
};
const p = this.parseRequestParameters(req.query, constraints);
if (p.error) {
res.status(400).json(this.getBisqMarketErrorResponse(p.error));
return;
}
const result = marketsApi.getDepth(p.market);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketDepth error'));
}
}
private getBisqMarketMarkets(req: Request, res: Response) {
const result = marketsApi.getMarkets();
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketMarkets error'));
}
}
private getBisqMarketTrades(req: Request, res: Response) {
const constraints: RequiredSpec = {
'market': {
required: true,
types: ['@string']
},
'timestamp_from': {
required: false,
types: ['@number']
},
'timestamp_to': {
required: false,
types: ['@number']
},
'trade_id_to': {
required: false,
types: ['@string']
},
'trade_id_from': {
required: false,
types: ['@string']
},
'direction': {
required: false,
types: ['buy', 'sell']
},
'limit': {
required: false,
types: ['@number']
},
'sort': {
required: false,
types: ['asc', 'desc']
}
};
const p = this.parseRequestParameters(req.query, constraints);
if (p.error) {
res.status(400).json(this.getBisqMarketErrorResponse(p.error));
return;
}
const result = marketsApi.getTrades(p.market, p.timestamp_from,
p.timestamp_to, p.trade_id_from, p.trade_id_to, p.direction, p.limit, p.sort);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketTrades error'));
}
}
private getBisqMarketOffers(req: Request, res: Response) {
const constraints: RequiredSpec = {
'market': {
required: true,
types: ['@string']
},
'direction': {
required: false,
types: ['buy', 'sell']
},
};
const p = this.parseRequestParameters(req.query, constraints);
if (p.error) {
res.status(400).json(this.getBisqMarketErrorResponse(p.error));
return;
}
const result = marketsApi.getOffers(p.market, p.direction);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketOffers error'));
}
}
private getBisqMarketVolumes(req: Request, res: Response) {
const constraints: RequiredSpec = {
'market': {
required: false,
types: ['@string']
},
'interval': {
required: false,
types: ['minute', 'half_hour', 'hour', 'half_day', 'day', 'week', 'month', 'year', 'auto']
},
'timestamp_from': {
required: false,
types: ['@number']
},
'timestamp_to': {
required: false,
types: ['@number']
},
'milliseconds': {
required: false,
types: ['@boolean']
},
'timestamp': {
required: false,
types: ['no', 'yes']
},
};
const p = this.parseRequestParameters(req.query, constraints);
if (p.error) {
res.status(400).json(this.getBisqMarketErrorResponse(p.error));
return;
}
const result = marketsApi.getVolumes(p.market, p.timestamp_from, p.timestamp_to, p.interval, p.milliseconds, p.timestamp);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketVolumes error'));
}
}
private getBisqMarketHloc(req: Request, res: Response) {
const constraints: RequiredSpec = {
'market': {
required: true,
types: ['@string']
},
'interval': {
required: false,
types: ['minute', 'half_hour', 'hour', 'half_day', 'day', 'week', 'month', 'year', 'auto']
},
'timestamp_from': {
required: false,
types: ['@number']
},
'timestamp_to': {
required: false,
types: ['@number']
},
'milliseconds': {
required: false,
types: ['@boolean']
},
'timestamp': {
required: false,
types: ['no', 'yes']
},
};
const p = this.parseRequestParameters(req.query, constraints);
if (p.error) {
res.status(400).json(this.getBisqMarketErrorResponse(p.error));
return;
}
const result = marketsApi.getHloc(p.market, p.interval, p.timestamp_from, p.timestamp_to, p.milliseconds, p.timestamp);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketHloc error'));
}
}
private getBisqMarketTicker(req: Request, res: Response) {
const constraints: RequiredSpec = {
'market': {
required: false,
types: ['@string']
},
};
const p = this.parseRequestParameters(req.query, constraints);
if (p.error) {
res.status(400).json(this.getBisqMarketErrorResponse(p.error));
return;
}
const result = marketsApi.getTicker(p.market);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketTicker error'));
}
}
private getBisqMarketVolumes7d(req: Request, res: Response) {
const result = marketsApi.getVolumesByTime(604800);
if (result) {
res.json(result);
} else {
res.status(500).json(this.getBisqMarketErrorResponse('getBisqMarketVolumes7d error'));
}
}
private parseRequestParameters(requestParams: object, params: RequiredSpec): { [name: string]: any; } {
const final = {};
for (const i in params) {
if (params.hasOwnProperty(i)) {
if (params[i].required && requestParams[i] === undefined) {
return { error: i + ' parameter missing'};
}
if (typeof requestParams[i] === 'string') {
const str = (requestParams[i] || '').toString().toLowerCase();
if (params[i].types.indexOf('@number') > -1) {
const number = parseInt((str).toString(), 10);
final[i] = number;
} else if (params[i].types.indexOf('@string') > -1) {
final[i] = str;
} else if (params[i].types.indexOf('@boolean') > -1) {
final[i] = str === 'true' || str === 'yes';
} else if (params[i].types.indexOf(str) > -1) {
final[i] = str;
} else {
return { error: i + ' parameter invalid'};
}
} else if (typeof requestParams[i] === 'number') {
final[i] = requestParams[i];
}
}
}
return final;
}
private getBisqMarketErrorResponse(message: string): MarketsApiError {
return {
'success': 0,
'error': message
};
}
}
export default new BisqRoutes;

View File

@@ -1,359 +0,0 @@
import config from '../../config';
import * as fs from 'fs';
import axios, { AxiosResponse } from 'axios';
import * as http from 'http';
import * as https from 'https';
import { SocksProxyAgent } from 'socks-proxy-agent';
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces';
import { Common } from '../common';
import { BlockExtended } from '../../mempool.interfaces';
import backendInfo from '../backend-info';
import logger from '../../logger';
class Bisq {
private static BLOCKS_JSON_FILE_PATH = config.BISQ.DATA_PATH + '/json/all/blocks.json';
private latestBlockHeight = 0;
private blocks: BisqBlock[] = [];
private allBlocks: BisqBlock[] = [];
private transactions: BisqTransaction[] = [];
private transactionIndex: { [txId: string]: BisqTransaction } = {};
private blockIndex: { [hash: string]: BisqBlock } = {};
private addressIndex: { [address: string]: BisqTransaction[] } = {};
private stats: BisqStats = {
minted: 0,
burnt: 0,
addresses: 0,
unspent_txos: 0,
spent_txos: 0,
};
private price: number = 0;
private priceUpdateCallbackFunction: ((price: number) => void) | undefined;
private topDirectoryWatcher: fs.FSWatcher | undefined;
private subdirectoryWatcher: fs.FSWatcher | undefined;
constructor() {}
startBisqService(): void {
try {
this.checkForBisqDataFolder();
} catch (e) {
logger.info('Retrying to start bisq service in 3 minutes');
setTimeout(this.startBisqService.bind(this), 180000);
return;
}
this.loadBisqDumpFile();
setInterval(this.updatePrice.bind(this), 1000 * 60 * 60);
this.updatePrice();
this.startTopDirectoryWatcher();
this.startSubDirectoryWatcher();
}
handleNewBitcoinBlock(block: BlockExtended): void {
if (block.height - 10 > this.latestBlockHeight && this.latestBlockHeight !== 0) {
logger.warn(`Bitcoin block height (#${block.height}) has diverged from the latest Bisq block height (#${this.latestBlockHeight}). Restarting watchers...`);
this.startTopDirectoryWatcher();
this.startSubDirectoryWatcher();
}
}
getTransaction(txId: string): BisqTransaction | undefined {
return this.transactionIndex[txId];
}
getTransactions(start: number, length: number, types: string[]): [BisqTransaction[], number] {
let transactions = this.transactions;
if (types.length) {
transactions = transactions.filter((tx) => types.indexOf(tx.txType) > -1);
}
return [transactions.slice(start, length + start), transactions.length];
}
getBlock(hash: string): BisqBlock | undefined {
return this.blockIndex[hash];
}
getAddress(hash: string): BisqTransaction[] {
return this.addressIndex[hash];
}
getBlocks(start: number, length: number): [BisqBlock[], number] {
return [this.blocks.slice(start, length + start), this.blocks.length];
}
getStats(): BisqStats {
return this.stats;
}
setPriceCallbackFunction(fn: (price: number) => void) {
this.priceUpdateCallbackFunction = fn;
}
getLatestBlockHeight(): number {
return this.latestBlockHeight;
}
private checkForBisqDataFolder() {
if (!fs.existsSync(Bisq.BLOCKS_JSON_FILE_PATH)) {
logger.warn(Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
throw new Error(`Cannot load BISQ ${Bisq.BLOCKS_JSON_FILE_PATH} file`);
}
}
private startTopDirectoryWatcher() {
if (this.topDirectoryWatcher) {
this.topDirectoryWatcher.close();
}
let fsWait: NodeJS.Timeout | null = null;
this.topDirectoryWatcher = fs.watch(config.BISQ.DATA_PATH + '/json', () => {
if (fsWait) {
clearTimeout(fsWait);
}
if (this.subdirectoryWatcher) {
this.subdirectoryWatcher.close();
}
fsWait = setTimeout(() => {
logger.debug(`Bisq restart detected. Resetting both watchers in 3 minutes.`);
setTimeout(() => {
this.startTopDirectoryWatcher();
this.startSubDirectoryWatcher();
this.loadBisqDumpFile();
}, 180000);
}, 15000);
});
}
private startSubDirectoryWatcher() {
if (this.subdirectoryWatcher) {
this.subdirectoryWatcher.close();
}
if (!fs.existsSync(Bisq.BLOCKS_JSON_FILE_PATH)) {
logger.warn(Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
setTimeout(() => this.startSubDirectoryWatcher(), 180000);
return;
}
let fsWait: NodeJS.Timeout | null = null;
this.subdirectoryWatcher = fs.watch(config.BISQ.DATA_PATH + '/json/all', () => {
if (fsWait) {
clearTimeout(fsWait);
}
fsWait = setTimeout(() => {
logger.debug(`Change detected in the Bisq data folder.`);
this.loadBisqDumpFile();
}, 2000);
});
}
private async updatePrice() {
type axiosOptions = {
headers: {
'User-Agent': string
};
timeout: number;
httpAgent?: http.Agent;
httpsAgent?: https.Agent;
}
const setDelay = (secs: number = 1): Promise<void> => new Promise(resolve => setTimeout(() => resolve(), secs * 1000));
const BISQ_URL = (config.SOCKS5PROXY.ENABLED === true) && (config.SOCKS5PROXY.USE_ONION === true) ? config.EXTERNAL_DATA_SERVER.BISQ_ONION : config.EXTERNAL_DATA_SERVER.BISQ_URL;
const isHTTP = (new URL(BISQ_URL).protocol.split(':')[0] === 'http') ? true : false;
const axiosOptions: axiosOptions = {
headers: {
'User-Agent': (config.MEMPOOL.USER_AGENT === 'mempool') ? `mempool/v${backendInfo.getBackendInfo().version}` : `${config.MEMPOOL.USER_AGENT}`
},
timeout: config.SOCKS5PROXY.ENABLED ? 30000 : 10000
};
let retry = 0;
while(retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) {
try {
if (config.SOCKS5PROXY.ENABLED) {
const socksOptions: any = {
agentOptions: {
keepAlive: true,
},
hostname: config.SOCKS5PROXY.HOST,
port: config.SOCKS5PROXY.PORT
};
if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) {
socksOptions.username = config.SOCKS5PROXY.USERNAME;
socksOptions.password = config.SOCKS5PROXY.PASSWORD;
} else {
// Retry with different tor circuits https://stackoverflow.com/a/64960234
socksOptions.username = `circuit${retry}`;
}
// Handle proxy agent for onion addresses
if (isHTTP) {
axiosOptions.httpAgent = new SocksProxyAgent(socksOptions);
} else {
axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions);
}
}
const data: AxiosResponse = await axios.get(`${BISQ_URL}/trades/?market=bsq_btc`, axiosOptions);
if (data.statusText === 'error' || !data.data) {
throw new Error(`Could not fetch data from Bisq market, Error: ${data.status}`);
}
const prices: number[] = [];
data.data.forEach((trade) => {
prices.push(parseFloat(trade.price) * 100000000);
});
prices.sort((a, b) => a - b);
this.price = Common.median(prices);
if (this.priceUpdateCallbackFunction) {
this.priceUpdateCallbackFunction(this.price);
}
logger.debug('Successfully updated Bisq market price');
break;
} catch (e) {
logger.err('Error updating Bisq market price: ' + (e instanceof Error ? e.message : e));
await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL);
retry++;
}
}
}
private async loadBisqDumpFile(): Promise<void> {
this.allBlocks = [];
try {
await this.loadData();
this.buildIndex();
this.calculateStats();
} catch (e) {
logger.info('Cannot load bisq dump file because: ' + (e instanceof Error ? e.message : e));
}
}
private buildIndex() {
const start = new Date().getTime();
this.transactions = [];
this.transactionIndex = {};
this.addressIndex = {};
this.allBlocks.forEach((block) => {
/* Build block index */
if (!this.blockIndex[block.hash]) {
this.blockIndex[block.hash] = block;
}
/* Build transactions index */
block.txs.forEach((tx) => {
this.transactions.push(tx);
this.transactionIndex[tx.id] = tx;
});
});
/* Build address index */
this.transactions.forEach((tx) => {
tx.inputs.forEach((input) => {
if (!this.addressIndex[input.address]) {
this.addressIndex[input.address] = [];
}
if (this.addressIndex[input.address].indexOf(tx) === -1) {
this.addressIndex[input.address].push(tx);
}
});
tx.outputs.forEach((output) => {
if (!this.addressIndex[output.address]) {
this.addressIndex[output.address] = [];
}
if (this.addressIndex[output.address].indexOf(tx) === -1) {
this.addressIndex[output.address].push(tx);
}
});
});
const time = new Date().getTime() - start;
logger.debug('Bisq data index rebuilt in ' + time + ' ms');
}
private calculateStats() {
let minted = 0;
let burned = 0;
let unspent = 0;
let spent = 0;
this.transactions.forEach((tx) => {
tx.outputs.forEach((output) => {
if (output.opReturn) {
return;
}
if (output.txOutputType === 'GENESIS_OUTPUT' || output.txOutputType === 'ISSUANCE_CANDIDATE_OUTPUT' && output.isVerified) {
minted += output.bsqAmount;
}
if (output.isUnspent) {
unspent++;
} else {
spent++;
}
});
burned += tx['burntFee'];
});
this.stats = {
addresses: Object.keys(this.addressIndex).length,
minted: minted / 100,
burnt: burned / 100,
spent_txos: spent,
unspent_txos: unspent,
};
}
private async loadData(): Promise<any> {
if (!fs.existsSync(Bisq.BLOCKS_JSON_FILE_PATH)) {
throw new Error(Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist`);
}
const readline = require('readline');
const events = require('events');
const rl = readline.createInterface({
input: fs.createReadStream(Bisq.BLOCKS_JSON_FILE_PATH),
crlfDelay: Infinity
});
let blockBuffer = '';
let readingBlock = false;
let lineCount = 1;
const start = new Date().getTime();
logger.debug('Processing Bisq data dump...');
rl.on('line', (line) => {
if (lineCount === 2) {
line = line.replace(' "chainHeight": ', '');
this.latestBlockHeight = parseInt(line, 10);
}
if (line === ' {') {
readingBlock = true;
} else if (line === ' },') {
blockBuffer += '}';
try {
const block: BisqBlock = JSON.parse(blockBuffer);
this.allBlocks.push(block);
readingBlock = false;
blockBuffer = '';
} catch (e) {
logger.debug(blockBuffer);
throw Error(`Unable to parse Bisq data dump at line ${lineCount}` + (e instanceof Error ? e.message : e));
}
}
if (readingBlock === true) {
blockBuffer += line;
}
++lineCount;
});
await events.once(rl, 'close');
this.allBlocks.reverse();
this.blocks = this.allBlocks.filter((block) => block.txs.length > 0);
const time = new Date().getTime() - start;
logger.debug('Bisq dump processed in ' + time + ' ms');
}
}
export default new Bisq();

View File

@@ -1,258 +0,0 @@
export interface BisqBlocks {
chainHeight: number;
blocks: BisqBlock[];
}
export interface BisqBlock {
height: number;
time: number;
hash: string;
previousBlockHash: string;
txs: BisqTransaction[];
}
export interface BisqTransaction {
txVersion: string;
id: string;
blockHeight: number;
blockHash: string;
time: number;
inputs: BisqInput[];
outputs: BisqOutput[];
txType: string;
txTypeDisplayString: string;
burntFee: number;
invalidatedBsq: number;
unlockBlockHeight: number;
}
export interface BisqStats {
minted: number;
burnt: number;
addresses: number;
unspent_txos: number;
spent_txos: number;
}
interface BisqInput {
spendingTxOutputIndex: number;
spendingTxId: string;
bsqAmount: number;
isVerified: boolean;
address: string;
time: number;
}
interface BisqOutput {
txVersion: string;
txId: string;
index: number;
bsqAmount: number;
btcAmount: number;
height: number;
isVerified: boolean;
burntFee: number;
invalidatedBsq: number;
address: string;
scriptPubKey: BisqScriptPubKey;
time: any;
txType: string;
txTypeDisplayString: string;
txOutputType: string;
txOutputTypeDisplayString: string;
lockTime: number;
isUnspent: boolean;
spentInfo: SpentInfo;
opReturn?: string;
}
interface BisqScriptPubKey {
addresses: string[];
asm: string;
hex: string;
reqSigs?: number;
type: string;
}
interface SpentInfo {
height: number;
inputIndex: number;
txId: string;
}
export interface BisqTrade {
direction: string;
price: string;
amount: string;
volume: string;
payment_method: string;
trade_id: string;
trade_date: number;
market?: string;
}
export interface Currencies { [txid: string]: Currency; }
export interface Currency {
code: string;
name: string;
precision: number;
_type: string;
}
export interface Depth { [market: string]: Market; }
interface Market {
'buys': string[];
'sells': string[];
}
export interface HighLowOpenClose {
period_start: number | string;
open: string;
high: string;
low: string;
close: string;
volume_left: string;
volume_right: string;
avg: string;
}
export interface Markets { [txid: string]: Pair; }
interface Pair {
pair: string;
lname: string;
rname: string;
lsymbol: string;
rsymbol: string;
lprecision: number;
rprecision: number;
ltype: string;
rtype: string;
name: string;
}
export interface Offers { [market: string]: OffersMarket; }
interface OffersMarket {
buys: Offer[] | null;
sells: Offer[] | null;
}
export interface OffersData {
direction: string;
currencyCode: string;
minAmount: number;
amount: number;
price: number;
date: number;
useMarketBasedPrice: boolean;
marketPriceMargin: number;
paymentMethod: string;
id: string;
currencyPair: string;
primaryMarketDirection: string;
priceDisplayString: string;
primaryMarketAmountDisplayString: string;
primaryMarketMinAmountDisplayString: string;
primaryMarketVolumeDisplayString: string;
primaryMarketMinVolumeDisplayString: string;
primaryMarketPrice: number;
primaryMarketAmount: number;
primaryMarketMinAmount: number;
primaryMarketVolume: number;
primaryMarketMinVolume: number;
}
export interface Offer {
offer_id: string;
offer_date: number;
direction: string;
min_amount: string;
amount: string;
price: string;
volume: string;
payment_method: string;
offer_fee_txid: any;
}
export interface Tickers { [market: string]: Ticker | null; }
export interface Ticker {
last: string;
high: string;
low: string;
volume_left: string;
volume_right: string;
buy: string | null;
sell: string | null;
}
export interface Trade {
direction: string;
price: string;
amount: string;
volume: string;
payment_method: string;
trade_id: string;
trade_date: number;
}
export interface TradesData {
currency: string;
direction: string;
tradePrice: number;
tradeAmount: number;
tradeDate: number;
paymentMethod: string;
offerDate: number;
useMarketBasedPrice: boolean;
marketPriceMargin: number;
offerAmount: number;
offerMinAmount: number;
offerId: string;
depositTxId?: string;
currencyPair: string;
primaryMarketDirection: string;
primaryMarketTradePrice: number;
primaryMarketTradeAmount: number;
primaryMarketTradeVolume: number;
_market: string;
_tradePriceStr: string;
_tradeAmountStr: string;
_tradeVolumeStr: string;
_offerAmountStr: string;
_tradePrice: number;
_tradeAmount: number;
_tradeVolume: number;
_offerAmount: number;
}
export interface MarketVolume {
period_start: number;
num_trades: number;
volume: string;
}
export interface MarketsApiError {
success: number;
error: string;
}
export type Interval = 'minute' | 'half_hour' | 'hour' | 'half_day' | 'day' | 'week' | 'month' | 'year' | 'auto';
export interface SummarizedIntervals { [market: string]: SummarizedInterval; }
export interface SummarizedInterval {
'period_start': number;
'open': number;
'close': number;
'high': number;
'low': number;
'avg': number;
'volume_right': number;
'volume_left': number;
}

View File

@@ -1,679 +0,0 @@
import { Currencies, OffersData, TradesData, Depth, Currency, Interval, HighLowOpenClose,
Markets, Offers, Offer, BisqTrade, MarketVolume, Tickers, Ticker, SummarizedIntervals, SummarizedInterval } from './interfaces';
const strtotime = require('./strtotime');
class BisqMarketsApi {
private cryptoCurrencyData: Currency[] = [];
private fiatCurrencyData: Currency[] = [];
private activeCryptoCurrencyData: Currency[] = [];
private activeFiatCurrencyData: Currency[] = [];
private offersData: OffersData[] = [];
private tradesData: TradesData[] = [];
private fiatCurrenciesIndexed: { [code: string]: true } = {};
private allCurrenciesIndexed: { [code: string]: Currency } = {};
private tradeDataByMarket: { [market: string]: TradesData[] } = {};
private tickersCache: Ticker | Tickers | null = null;
constructor() { }
setOffersData(offers: OffersData[]) {
this.offersData = offers;
}
setTradesData(trades: TradesData[]) {
this.tradesData = trades;
this.tradeDataByMarket = {};
this.tradesData.forEach((trade) => {
trade._market = trade.currencyPair.toLowerCase().replace('/', '_');
if (!this.tradeDataByMarket[trade._market]) {
this.tradeDataByMarket[trade._market] = [];
}
this.tradeDataByMarket[trade._market].push(trade);
});
}
setCurrencyData(cryptoCurrency: Currency[], fiatCurrency: Currency[], activeCryptoCurrency: Currency[], activeFiatCurrency: Currency[]) {
this.cryptoCurrencyData = cryptoCurrency,
this.fiatCurrencyData = fiatCurrency,
this.activeCryptoCurrencyData = activeCryptoCurrency,
this.activeFiatCurrencyData = activeFiatCurrency;
this.fiatCurrenciesIndexed = {};
this.allCurrenciesIndexed = {};
this.fiatCurrencyData.forEach((currency) => {
currency._type = 'fiat';
this.fiatCurrenciesIndexed[currency.code] = true;
this.allCurrenciesIndexed[currency.code] = currency;
});
this.cryptoCurrencyData.forEach((currency) => {
currency._type = 'crypto';
this.allCurrenciesIndexed[currency.code] = currency;
});
}
updateCache() {
this.tickersCache = null;
this.tickersCache = this.getTicker();
}
getCurrencies(
type: 'crypto' | 'fiat' | 'active' | 'all' = 'all',
): Currencies {
let currencies: Currency[];
switch (type) {
case 'fiat':
currencies = this.fiatCurrencyData;
break;
case 'crypto':
currencies = this.cryptoCurrencyData;
break;
case 'active':
currencies = this.activeCryptoCurrencyData.concat(this.activeFiatCurrencyData);
break;
case 'all':
default:
currencies = this.cryptoCurrencyData.concat(this.fiatCurrencyData);
}
const result = {};
currencies.forEach((currency) => {
result[currency.code] = currency;
});
return result;
}
getDepth(
market: string,
): Depth {
const currencyPair = market.replace('_', '/').toUpperCase();
const buys = this.offersData
.filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'BUY')
.map((offer) => offer.price)
.sort((a, b) => b - a)
.map((price) => this.intToBtc(price));
const sells = this.offersData
.filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'SELL')
.map((offer) => offer.price)
.sort((a, b) => a - b)
.map((price) => this.intToBtc(price));
const result = {};
result[market] = {
'buys': buys,
'sells': sells,
};
return result;
}
getOffers(
market: string,
direction?: 'buy' | 'sell',
): Offers {
const currencyPair = market.replace('_', '/').toUpperCase();
let buys: Offer[] | null = null;
let sells: Offer[] | null = null;
if (!direction || direction === 'buy') {
buys = this.offersData
.filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'BUY')
.sort((a, b) => b.price - a.price)
.map((offer) => this.offerDataToOffer(offer, market));
}
if (!direction || direction === 'sell') {
sells = this.offersData
.filter((offer) => offer.currencyPair === currencyPair && offer.primaryMarketDirection === 'SELL')
.sort((a, b) => a.price - b.price)
.map((offer) => this.offerDataToOffer(offer, market));
}
const result: Offers = {};
result[market] = {
'buys': buys,
'sells': sells,
};
return result;
}
getMarkets(): Markets {
const allCurrencies = this.getCurrencies();
const activeCurrencies = this.getCurrencies('active');
const markets = {};
for (const currency of Object.keys(activeCurrencies)) {
if (allCurrencies[currency].code === 'BTC') {
continue;
}
const isFiat = allCurrencies[currency]._type === 'fiat';
const pmarketname = allCurrencies['BTC']['name'];
const lsymbol = isFiat ? 'BTC' : currency;
const rsymbol = isFiat ? currency : 'BTC';
const lname = isFiat ? pmarketname : allCurrencies[currency].name;
const rname = isFiat ? allCurrencies[currency].name : pmarketname;
const ltype = isFiat ? 'crypto' : allCurrencies[currency]._type;
const rtype = isFiat ? 'fiat' : 'crypto';
const lprecision = 8;
const rprecision = isFiat ? 2 : 8;
const pair = lsymbol.toLowerCase() + '_' + rsymbol.toLowerCase();
markets[pair] = {
'pair': pair,
'lname': lname,
'rname': rname,
'lsymbol': lsymbol,
'rsymbol': rsymbol,
'lprecision': lprecision,
'rprecision': rprecision,
'ltype': ltype,
'rtype': rtype,
'name': lname + '/' + rname,
};
}
return markets;
}
getTrades(
market: string,
timestamp_from?: number,
timestamp_to?: number,
trade_id_from?: string,
trade_id_to?: string,
direction?: 'buy' | 'sell',
limit: number = 100,
sort: 'asc' | 'desc' = 'desc',
): BisqTrade[] {
limit = Math.min(limit, 2000);
const _market = market === 'all' ? undefined : market;
if (!timestamp_from) {
timestamp_from = new Date('2016-01-01').getTime() / 1000;
}
if (!timestamp_to) {
timestamp_to = new Date().getTime() / 1000;
}
const matches = this.getTradesByCriteria(_market, timestamp_to, timestamp_from,
trade_id_to, trade_id_from, direction, sort, limit, false);
if (sort === 'asc') {
matches.sort((a, b) => a.tradeDate - b.tradeDate);
} else {
matches.sort((a, b) => b.tradeDate - a.tradeDate);
}
return matches.map((trade) => {
const bsqTrade: BisqTrade = {
direction: trade.primaryMarketDirection,
price: trade._tradePriceStr,
amount: trade._tradeAmountStr,
volume: trade._tradeVolumeStr,
payment_method: trade.paymentMethod,
trade_id: trade.offerId,
trade_date: trade.tradeDate,
};
if (market === 'all') {
bsqTrade.market = trade._market;
}
return bsqTrade;
});
}
getVolumes(
market?: string,
timestamp_from?: number,
timestamp_to?: number,
interval: Interval = 'auto',
milliseconds?: boolean,
timestamp: 'no' | 'yes' = 'yes',
): MarketVolume[] {
if (milliseconds) {
timestamp_from = timestamp_from ? timestamp_from / 1000 : timestamp_from;
timestamp_to = timestamp_to ? timestamp_to / 1000 : timestamp_to;
}
if (!timestamp_from) {
timestamp_from = new Date('2016-01-01').getTime() / 1000;
}
if (!timestamp_to) {
timestamp_to = new Date().getTime() / 1000;
}
const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from,
undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
if (interval === 'auto') {
const range = timestamp_to - timestamp_from;
interval = this.getIntervalFromRange(range);
}
const intervals: any = {};
const marketVolumes: MarketVolume[] = [];
for (const trade of trades) {
const traded_at = trade['tradeDate'] / 1000;
const interval_start = this.intervalStart(traded_at, interval);
if (!intervals[interval_start]) {
intervals[interval_start] = {
'volume': 0,
'num_trades': 0,
};
}
const period = intervals[interval_start];
period['period_start'] = interval_start;
period['volume'] += this.fiatCurrenciesIndexed[trade.currency] ? trade._tradeAmount : trade._tradeVolume;
period['num_trades']++;
}
for (const p in intervals) {
if (intervals.hasOwnProperty(p)) {
const period = intervals[p];
marketVolumes.push({
period_start: timestamp === 'no' ? new Date(period['period_start'] * 1000).toISOString() : period['period_start'],
num_trades: period['num_trades'],
volume: this.intToBtc(period['volume']),
});
}
}
return marketVolumes;
}
getTicker(
market?: string,
): Tickers | Ticker | null {
if (market) {
return this.getTickerFromMarket(market);
}
if (this.tickersCache) {
return this.tickersCache;
}
const allMarkets = this.getMarkets();
const tickers = {};
for (const m in allMarkets) {
if (allMarkets.hasOwnProperty(m)) {
tickers[allMarkets[m].pair] = this.getTickerFromMarket(allMarkets[m].pair);
}
}
return tickers;
}
getTickerFromMarket(market: string): Ticker | null {
let ticker: Ticker;
const timestamp_from = strtotime('-24 hour');
const timestamp_to = new Date().getTime() / 1000;
const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from,
undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
const periods: SummarizedInterval[] = Object.values(this.getTradesSummarized(trades, timestamp_from));
const allCurrencies = this.getCurrencies();
const currencyRight = allCurrencies[market.split('_')[1].toUpperCase()];
if (periods[0]) {
ticker = {
'last': this.intToBtc(periods[0].close),
'high': this.intToBtc(periods[0].high),
'low': this.intToBtc(periods[0].low),
'volume_left': this.intToBtc(periods[0].volume_left),
'volume_right': this.intToBtc(periods[0].volume_right),
'buy': null,
'sell': null,
};
} else {
const lastTrade = this.tradeDataByMarket[market];
if (!lastTrade) {
return null;
}
const tradePrice = lastTrade[0].primaryMarketTradePrice * Math.pow(10, 8 - currencyRight.precision);
const lastTradePrice = this.intToBtc(tradePrice);
ticker = {
'last': lastTradePrice,
'high': lastTradePrice,
'low': lastTradePrice,
'volume_left': '0',
'volume_right': '0',
'buy': null,
'sell': null,
};
}
const timestampFromMilli = timestamp_from * 1000;
const timestampToMilli = timestamp_to * 1000;
const currencyPair = market.replace('_', '/').toUpperCase();
const offersData = this.offersData.slice().sort((a, b) => a.price - b.price);
const buy = offersData.find((offer) => offer.currencyPair === currencyPair
&& offer.primaryMarketDirection === 'BUY'
&& offer.date >= timestampFromMilli
&& offer.date <= timestampToMilli
);
const sell = offersData.find((offer) => offer.currencyPair === currencyPair
&& offer.primaryMarketDirection === 'SELL'
&& offer.date >= timestampFromMilli
&& offer.date <= timestampToMilli
);
if (buy) {
ticker.buy = this.intToBtc(buy.primaryMarketPrice * Math.pow(10, 8 - currencyRight.precision));
}
if (sell) {
ticker.sell = this.intToBtc(sell.primaryMarketPrice * Math.pow(10, 8 - currencyRight.precision));
}
return ticker;
}
getHloc(
market: string,
interval: Interval = 'auto',
timestamp_from?: number,
timestamp_to?: number,
milliseconds?: boolean,
timestamp: 'no' | 'yes' = 'yes',
): HighLowOpenClose[] {
if (milliseconds) {
timestamp_from = timestamp_from ? timestamp_from / 1000 : timestamp_from;
timestamp_to = timestamp_to ? timestamp_to / 1000 : timestamp_to;
}
if (!timestamp_from) {
timestamp_from = new Date('2016-01-01').getTime() / 1000;
}
if (!timestamp_to) {
timestamp_to = new Date().getTime() / 1000;
}
const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from,
undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
if (interval === 'auto') {
const range = timestamp_to - timestamp_from;
interval = this.getIntervalFromRange(range);
}
const intervals = this.getTradesSummarized(trades, timestamp_from, interval);
const hloc: HighLowOpenClose[] = [];
for (const p in intervals) {
if (intervals.hasOwnProperty(p)) {
const period = intervals[p];
hloc.push({
period_start: timestamp === 'no' ? new Date(period['period_start'] * 1000).toISOString() : period['period_start'],
open: this.intToBtc(period['open']),
close: this.intToBtc(period['close']),
high: this.intToBtc(period['high']),
low: this.intToBtc(period['low']),
avg: this.intToBtc(period['avg']),
volume_right: this.intToBtc(period['volume_right']),
volume_left: this.intToBtc(period['volume_left']),
});
}
}
return hloc;
}
private getIntervalFromRange(range: number): Interval {
// two days range loads minute data
if (range <= 3600) {
// up to one hour range loads minutely data
return 'minute';
} else if (range <= 1 * 24 * 3600) {
// up to one day range loads half-hourly data
return 'half_hour';
} else if (range <= 3 * 24 * 3600) {
// up to 3 day range loads hourly data
return 'hour';
} else if (range <= 7 * 24 * 3600) {
// up to 7 day range loads half-daily data
return 'half_day';
} else if (range <= 60 * 24 * 3600) {
// up to 2 month range loads daily data
return 'day';
} else if (range <= 12 * 31 * 24 * 3600) {
// up to one year range loads weekly data
return 'week';
} else if (range <= 12 * 31 * 24 * 3600) {
// up to 5 year range loads monthly data
return 'month';
} else {
// greater range loads yearly data
return 'year';
}
}
getVolumesByTime(time: number): MarketVolume[] {
const timestamp_from = new Date().getTime() / 1000 - time;
const timestamp_to = new Date().getTime() / 1000;
const trades = this.getTradesByCriteria(undefined, timestamp_to, timestamp_from,
undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER);
const markets: any = {};
for (const trade of trades) {
if (!markets[trade._market]) {
markets[trade._market] = {
'volume': 0,
'num_trades': 0,
};
}
markets[trade._market]['volume'] += this.fiatCurrenciesIndexed[trade.currency] ? trade._tradeAmount : trade._tradeVolume;
markets[trade._market]['num_trades']++;
}
return markets;
}
private getTradesSummarized(trades: TradesData[], timestamp_from: number, interval?: string): SummarizedIntervals {
const intervals: any = {};
const intervals_prices: any = {};
for (const trade of trades) {
const traded_at = trade.tradeDate / 1000;
const interval_start = !interval ? timestamp_from : this.intervalStart(traded_at, interval);
if (!intervals[interval_start]) {
intervals[interval_start] = {
'open': 0,
'close': 0,
'high': 0,
'low': 0,
'avg': 0,
'volume_right': 0,
'volume_left': 0,
};
intervals_prices[interval_start] = [];
}
const period = intervals[interval_start];
const price = trade._tradePrice;
if (!intervals_prices[interval_start]['leftvol']) {
intervals_prices[interval_start]['leftvol'] = [];
}
if (!intervals_prices[interval_start]['rightvol']) {
intervals_prices[interval_start]['rightvol'] = [];
}
intervals_prices[interval_start]['leftvol'].push(trade._tradeAmount);
intervals_prices[interval_start]['rightvol'].push(trade._tradeVolume);
if (price) {
const plow = period['low'];
period['period_start'] = interval_start;
period['open'] = period['open'] || price;
period['close'] = price;
period['high'] = price > period['high'] ? price : period['high'];
period['low'] = (plow && price > plow) ? period['low'] : price;
period['avg'] = intervals_prices[interval_start]['rightvol'].reduce((p: number, c: number) => c + p, 0)
/ intervals_prices[interval_start]['leftvol'].reduce((c: number, p: number) => c + p, 0) * 100000000;
period['volume_left'] += trade._tradeAmount;
period['volume_right'] += trade._tradeVolume;
}
}
return intervals;
}
private getTradesByCriteria(
market: string | undefined,
timestamp_to: number,
timestamp_from: number,
trade_id_to: string | undefined,
trade_id_from: string | undefined,
direction: 'buy' | 'sell' | undefined,
sort: string,
limit: number,
integerAmounts: boolean = true,
): TradesData[] {
let trade_id_from_ts: number | null = null;
let trade_id_to_ts: number | null = null;
const allCurrencies = this.getCurrencies();
const timestampFromMilli = timestamp_from * 1000;
const timestampToMilli = timestamp_to * 1000;
// note: the offer_id_from/to depends on iterating over trades in
// descending chronological order.
const tradesDataSorted = this.tradesData.slice();
if (sort === 'asc') {
tradesDataSorted.reverse();
}
let matches: TradesData[] = [];
for (const trade of tradesDataSorted) {
if (trade_id_from === trade.offerId) {
trade_id_from_ts = trade.tradeDate;
}
if (trade_id_to === trade.offerId) {
trade_id_to_ts = trade.tradeDate;
}
if (trade_id_to && trade_id_to_ts === null) {
continue;
}
if (trade_id_from && trade_id_from_ts != null && trade_id_from_ts !== trade.tradeDate) {
continue;
}
if (market && market !== trade._market) {
continue;
}
if (timestampFromMilli && timestampFromMilli > trade.tradeDate) {
continue;
}
if (timestampToMilli && timestampToMilli < trade.tradeDate) {
continue;
}
if (direction && direction !== trade.direction.toLowerCase()) {
continue;
}
// Filter out bogus trades with BTC/BTC or XXX/XXX market.
// See github issue: https://github.com/bitsquare/bitsquare/issues/883
const currencyPairs = trade.currencyPair.split('/');
if (currencyPairs[0] === currencyPairs[1]) {
continue;
}
const currencyLeft = allCurrencies[currencyPairs[0]];
const currencyRight = allCurrencies[currencyPairs[1]];
if (!currencyLeft || !currencyRight) {
continue;
}
const tradePrice = trade.primaryMarketTradePrice * Math.pow(10, 8 - currencyRight.precision);
const tradeAmount = trade.primaryMarketTradeAmount * Math.pow(10, 8 - currencyLeft.precision);
const tradeVolume = trade.primaryMarketTradeVolume * Math.pow(10, 8 - currencyRight.precision);
if (integerAmounts) {
trade._tradePrice = tradePrice;
trade._tradeAmount = tradeAmount;
trade._tradeVolume = tradeVolume;
trade._offerAmount = trade.offerAmount;
} else {
trade._tradePriceStr = this.intToBtc(tradePrice);
trade._tradeAmountStr = this.intToBtc(tradeAmount);
trade._tradeVolumeStr = this.intToBtc(tradeVolume);
trade._offerAmountStr = this.intToBtc(trade.offerAmount);
}
matches.push(trade);
if (matches.length >= limit) {
break;
}
}
if ((trade_id_from && !trade_id_from_ts) || (trade_id_to && !trade_id_to_ts)) {
matches = [];
}
return matches;
}
private intervalStart(ts: number, interval: string): number {
switch (interval) {
case 'minute':
return (ts - (ts % 60));
case '10_minute':
return (ts - (ts % 600));
case 'half_hour':
return (ts - (ts % 1800));
case 'hour':
return (ts - (ts % 3600));
case 'half_day':
return (ts - (ts % (3600 * 12)));
case 'day':
return strtotime('midnight today', ts);
case 'week':
return strtotime('midnight sunday last week', ts);
case 'month':
return strtotime('midnight first day of this month', ts);
case 'year':
return strtotime('midnight first day of january', ts);
default:
throw new Error('Unsupported interval');
}
}
private offerDataToOffer(offer: OffersData, market: string): Offer {
const currencyPairs = market.split('_');
const currencyRight = this.allCurrenciesIndexed[currencyPairs[1].toUpperCase()];
const currencyLeft = this.allCurrenciesIndexed[currencyPairs[0].toUpperCase()];
const price = offer['primaryMarketPrice'] * Math.pow( 10, 8 - currencyRight['precision']);
const amount = offer['primaryMarketAmount'] * Math.pow( 10, 8 - currencyLeft['precision']);
const volume = offer['primaryMarketVolume'] * Math.pow( 10, 8 - currencyRight['precision']);
return {
offer_id: offer.id,
offer_date: offer.date,
direction: offer.primaryMarketDirection,
min_amount: this.intToBtc(offer.minAmount),
amount: this.intToBtc(amount),
price: this.intToBtc(price),
volume: this.intToBtc(volume),
payment_method: offer.paymentMethod,
offer_fee_txid: null,
};
}
private intToBtc(val: number): string {
return (val / 100000000).toFixed(8);
}
}
export default new BisqMarketsApi();

View File

@@ -1,137 +0,0 @@
import config from '../../config';
import * as fs from 'fs';
import { OffersData as OffersData, TradesData, Currency } from './interfaces';
import bisqMarket from './markets-api';
import logger from '../../logger';
class Bisq {
private static FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE = 4000;
private static MARKET_JSON_PATH = config.BISQ.DATA_PATH;
private static MARKET_JSON_FILE_PATHS = {
activeCryptoCurrency: '/active_crypto_currency_list.json',
activeFiatCurrency: '/active_fiat_currency_list.json',
cryptoCurrency: '/crypto_currency_list.json',
fiatCurrency: '/fiat_currency_list.json',
offers: '/offers_statistics.json',
trades: '/trade_statistics.json',
};
private cryptoCurrencyLastMtime = new Date('2016-01-01');
private fiatCurrencyLastMtime = new Date('2016-01-01');
private offersLastMtime = new Date('2016-01-01');
private tradesLastMtime = new Date('2016-01-01');
private subdirectoryWatcher: fs.FSWatcher | undefined;
constructor() {}
startBisqService(): void {
try {
this.checkForBisqDataFolder();
} catch (e) {
logger.info('Retrying to start bisq service (markets) in 3 minutes');
setTimeout(this.startBisqService.bind(this), 180000);
return;
}
this.loadBisqDumpFile();
this.startBisqDirectoryWatcher();
}
private checkForBisqDataFolder() {
if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) {
logger.err(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
throw new Error(`Cannot load BISQ ${Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency} file`);
}
}
private startBisqDirectoryWatcher() {
if (this.subdirectoryWatcher) {
this.subdirectoryWatcher.close();
}
if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) {
logger.warn(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
setTimeout(() => this.startBisqDirectoryWatcher(), 180000);
return;
}
let fsWait: NodeJS.Timeout | null = null;
this.subdirectoryWatcher = fs.watch(Bisq.MARKET_JSON_PATH, () => {
if (fsWait) {
clearTimeout(fsWait);
}
fsWait = setTimeout(() => {
logger.debug(`Change detected in the Bisq market data folder.`);
this.loadBisqDumpFile();
}, Bisq.FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE);
});
}
private async loadBisqDumpFile(): Promise<void> {
const start = new Date().getTime();
try {
let marketsDataUpdated = false;
const cryptoMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency);
const fiatMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.fiatCurrency);
if (cryptoMtime > this.cryptoCurrencyLastMtime || fiatMtime > this.fiatCurrencyLastMtime) {
const cryptoCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency);
const fiatCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.fiatCurrency);
const activeCryptoCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.activeCryptoCurrency);
const activeFiatCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.activeFiatCurrency);
logger.debug('Updating Bisq Market Currency Data');
bisqMarket.setCurrencyData(cryptoCurrencyData, fiatCurrencyData, activeCryptoCurrencyData, activeFiatCurrencyData);
if (cryptoMtime > this.cryptoCurrencyLastMtime) {
this.cryptoCurrencyLastMtime = cryptoMtime;
}
if (fiatMtime > this.fiatCurrencyLastMtime) {
this.fiatCurrencyLastMtime = fiatMtime;
}
marketsDataUpdated = true;
}
const offersMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.offers);
if (offersMtime > this.offersLastMtime) {
const offersData = await this.loadData<OffersData[]>(Bisq.MARKET_JSON_FILE_PATHS.offers);
logger.debug('Updating Bisq Market Offers Data');
bisqMarket.setOffersData(offersData);
this.offersLastMtime = offersMtime;
marketsDataUpdated = true;
}
const tradesMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.trades);
if (tradesMtime > this.tradesLastMtime) {
const tradesData = await this.loadData<TradesData[]>(Bisq.MARKET_JSON_FILE_PATHS.trades);
logger.debug('Updating Bisq Market Trades Data');
bisqMarket.setTradesData(tradesData);
this.tradesLastMtime = tradesMtime;
marketsDataUpdated = true;
}
if (marketsDataUpdated) {
bisqMarket.updateCache();
const time = new Date().getTime() - start;
logger.debug('Bisq market data updated in ' + time + ' ms');
}
} catch (e) {
logger.err('loadBisqMarketDataDumpFile() error.' + (e instanceof Error ? e.message : e));
}
}
private getFileMtime(path: string): Date {
const stats = fs.statSync(Bisq.MARKET_JSON_PATH + path);
return stats.mtime;
}
private loadData<T>(path: string): Promise<T> {
return new Promise((resolve, reject) => {
fs.readFile(Bisq.MARKET_JSON_PATH + path, 'utf8', (err, data) => {
if (err) {
reject(err);
}
try {
const parsedData = JSON.parse(data);
resolve(parsedData);
} catch (e) {
reject('JSON parse error (' + path + ')');
}
});
});
}
}
export default new Bisq();

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { IBitcoinApi } from './bitcoin-api.interface';
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
import { IEsploraApi } from './esplora-api.interface';
export interface AbstractBitcoinApi {
@@ -22,6 +22,7 @@ export interface AbstractBitcoinApi {
$getScriptHash(scripthash: string): Promise<IEsploraApi.ScriptHash>;
$getScriptHashTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
$sendRawTransaction(rawTransaction: string): Promise<string>;
$testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]>;
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend>;
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]>;
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;

View File

@@ -205,3 +205,16 @@ export namespace IBitcoinApi {
"utxo_size_inc": number;
}
}
export interface TestMempoolAcceptResult {
txid: string,
wtxid: string,
allowed?: boolean,
vsize?: number,
fees?: {
base: number,
"effective-feerate": number,
"effective-includes": string[],
},
['reject-reason']?: string,
}

View File

@@ -1,6 +1,6 @@
import * as bitcoinjs from 'bitcoinjs-lib';
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
import { IBitcoinApi } from './bitcoin-api.interface';
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
import { IEsploraApi } from './esplora-api.interface';
import blocks from '../blocks';
import mempool from '../mempool';
@@ -174,6 +174,14 @@ class BitcoinApi implements AbstractBitcoinApi {
return this.bitcoindClient.sendRawTransaction(rawTransaction);
}
async $testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]> {
if (rawTransactions.length) {
return this.bitcoindClient.testMempoolAccept(rawTransactions, maxfeerate ?? undefined);
} else {
return [];
}
}
async $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
const txOut = await this.bitcoindClient.getTxOut(txId, vout, false);
return {

View File

@@ -37,60 +37,6 @@ class BitcoinRoutes {
.get(config.MEMPOOL.API_URL_PREFIX + 'replacements', this.getRbfReplacements)
.get(config.MEMPOOL.API_URL_PREFIX + 'fullrbf/replacements', this.getFullRbfReplacements)
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations`, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, {
responseType: 'stream', timeout: 10000
});
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors`, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, {
responseType: 'stream', timeout: 10000
});
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators`, { responseType: 'stream', timeout: 10000 });
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
try {
const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, {
responseType: 'stream', timeout: 10000
});
response.data.pipe(res);
} catch (e) {
res.status(500).end();
}
})
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks', this.getBlocks.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', this.getBlocks.bind(this))
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', this.getBlock)
@@ -109,6 +55,7 @@ class BitcoinRoutes {
.get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', this.getRecentMempoolTransactions)
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', this.getTransaction)
.post(config.MEMPOOL.API_URL_PREFIX + 'tx', this.$postTransaction)
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/test', this.$testTransactions)
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction)
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', this.getTransactionStatus)
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', this.getTransactionOutspends)
@@ -418,7 +365,7 @@ class BitcoinRoutes {
const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(await blocks.$getBlocks(height, 15));
} else { // Liquid, Bisq
} else { // Liquid
return await this.getLegacyBlocks(req, res);
}
} catch (e) {
@@ -428,7 +375,7 @@ class BitcoinRoutes {
private async getBlocksByBulk(req: Request, res: Response) {
try {
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid, Bisq - Not implemented
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid - Not implemented
return res.status(404).send(`This API is only available for Bitcoin networks`);
}
if (config.MEMPOOL.MAX_BLOCKS_BULK_QUERY <= 0) {
@@ -803,6 +750,19 @@ class BitcoinRoutes {
}
}
private async $testTransactions(req: Request, res: Response) {
try {
const rawTxs = Common.getTransactionsFromRequest(req);
const maxfeerate = parseFloat(req.query.maxfeerate as string);
const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate);
res.send(result);
} catch (e: any) {
res.setHeader('content-type', 'text/plain');
res.status(400).send(e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
}
}
}
export default new BitcoinRoutes();

View File

@@ -5,6 +5,7 @@ import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-fact
import { IEsploraApi } from './esplora-api.interface';
import logger from '../../logger';
import { Common } from '../common';
import { TestMempoolAcceptResult } from './bitcoin-api.interface';
interface FailoverHost {
host: string,
@@ -327,6 +328,10 @@ class ElectrsApi implements AbstractBitcoinApi {
throw new Error('Method not implemented.');
}
$testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]> {
throw new Error('Method not implemented.');
}
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
return this.failoverRouter.$get<IEsploraApi.Outspend>('/tx/' + txId + '/outspend/' + vout);
}

View File

@@ -29,6 +29,7 @@ import websocketHandler from './websocket-handler';
import redisCache from './redis-cache';
import rbfCache from './rbf-cache';
import { calcBitsDifference } from './difficulty-adjustment';
import AccelerationRepository from '../repositories/AccelerationRepository';
class Blocks {
private blocks: BlockExtended[] = [];
@@ -838,8 +839,11 @@ class Blocks {
} else {
this.currentBlockHeight++;
logger.debug(`New block found (#${this.currentBlockHeight})!`);
this.updateTimerProgress(timer, `getting orphaned blocks for ${this.currentBlockHeight}`);
await chainTips.updateOrphanedBlocks();
// skip updating the orphan block cache if we've fallen behind the chain tip
if (this.currentBlockHeight >= blockHeightTip - 2) {
this.updateTimerProgress(timer, `getting orphaned blocks for ${this.currentBlockHeight}`);
await chainTips.updateOrphanedBlocks();
}
}
this.updateTimerProgress(timer, `getting block data for ${this.currentBlockHeight}`);
@@ -872,6 +876,7 @@ class Blocks {
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
await HashratesRepository.$deleteLastEntries();
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
await AccelerationRepository.$deleteAccelerationsFrom(lastBlock.height - 10);
this.blocks = this.blocks.slice(0, -10);
this.updateTimerProgress(timer, `rolled back chain divergence from ${this.currentBlockHeight}`);
for (let i = 10; i >= 0; --i) {
@@ -974,6 +979,9 @@ class Blocks {
if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
}
blockSummary.transactions.forEach(tx => {
delete tx.acc;
});
this.blockSummaries.push(blockSummary);
if (this.blockSummaries.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
this.blockSummaries = this.blockSummaries.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
@@ -1117,6 +1125,7 @@ class Blocks {
}
return {
txid: tx.txid,
time: tx.firstSeen,
fee: tx.fee || 0,
vsize: tx.vsize,
value: Math.round(tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0)),

View File

@@ -12,32 +12,68 @@ export interface OrphanedBlock {
height: number;
hash: string;
status: 'valid-fork' | 'valid-headers' | 'headers-only';
prevhash: string;
}
class ChainTips {
private chainTips: ChainTip[] = [];
private orphanedBlocks: OrphanedBlock[] = [];
private orphanedBlocks: { [hash: string]: OrphanedBlock } = {};
private blockCache: { [hash: string]: OrphanedBlock } = {};
private orphansByHeight: { [height: number]: OrphanedBlock[] } = {};
public async updateOrphanedBlocks(): Promise<void> {
try {
this.chainTips = await bitcoinClient.getChainTips();
this.orphanedBlocks = [];
const start = Date.now();
const breakAt = start + 10000;
let newOrphans = 0;
this.orphanedBlocks = {};
for (const chain of this.chainTips) {
if (chain.status === 'valid-fork' || chain.status === 'valid-headers') {
let block = await bitcoinClient.getBlock(chain.hash);
while (block && block.confirmations === -1) {
this.orphanedBlocks.push({
height: block.height,
hash: block.hash,
status: chain.status
});
block = await bitcoinClient.getBlock(block.previousblockhash);
const orphans: OrphanedBlock[] = [];
let hash = chain.hash;
do {
let orphan = this.blockCache[hash];
if (!orphan) {
const block = await bitcoinClient.getBlock(hash);
if (block && block.confirmations === -1) {
newOrphans++;
orphan = {
height: block.height,
hash: block.hash,
status: chain.status,
prevhash: block.previousblockhash,
};
this.blockCache[hash] = orphan;
}
}
if (orphan) {
orphans.push(orphan);
}
hash = orphan?.prevhash;
} while (hash && (Date.now() < breakAt));
for (const orphan of orphans) {
this.orphanedBlocks[orphan.hash] = orphan;
}
}
if (Date.now() >= breakAt) {
logger.debug(`Breaking orphaned blocks updater after 10s, will continue next block`);
break;
}
}
logger.debug(`Updated orphaned blocks cache. Found ${this.orphanedBlocks.length} orphaned blocks`);
this.orphansByHeight = {};
const allOrphans = Object.values(this.orphanedBlocks);
for (const orphan of allOrphans) {
if (!this.orphansByHeight[orphan.height]) {
this.orphansByHeight[orphan.height] = [];
}
this.orphansByHeight[orphan.height].push(orphan);
}
logger.debug(`Updated orphaned blocks cache. Fetched ${newOrphans} new orphaned blocks. Total ${allOrphans.length}`);
} catch (e) {
logger.err(`Cannot get fetch orphaned blocks. Reason: ${e instanceof Error ? e.message : e}`);
}
@@ -48,13 +84,7 @@ class ChainTips {
return [];
}
const orphans: OrphanedBlock[] = [];
for (const block of this.orphanedBlocks) {
if (block.height === height) {
orphans.push(block);
}
}
return orphans;
return this.orphansByHeight[height] || [];
}
}

View File

@@ -373,6 +373,21 @@ export class Common {
].includes(pubkey);
}
static isInscription(vin, flags): bigint {
// in taproot, if the last witness item begins with 0x50, it's an annex
const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50');
// script spends have more than one witness item, not counting the annex (if present)
if (vin.witness.length > (hasAnnex ? 2 : 1)) {
// the script itself is the second-to-last witness item, not counting the annex
const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - (hasAnnex ? 3 : 2)]);
// inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope
if (asm?.includes('OP_0 OP_IF')) {
flags |= TransactionFlags.inscription;
}
}
return flags;
}
static getTransactionFlags(tx: TransactionExtended): number {
let flags = tx.flags ? BigInt(tx.flags) : 0n;
@@ -409,30 +424,31 @@ export class Common {
if (vin.sequence < 0xfffffffe) {
rbf = true;
}
switch (vin.prevout?.scriptpubkey_type) {
case 'p2pk': flags |= TransactionFlags.p2pk; break;
case 'multisig': flags |= TransactionFlags.p2ms; break;
case 'p2pkh': flags |= TransactionFlags.p2pkh; break;
case 'p2sh': flags |= TransactionFlags.p2sh; break;
case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break;
case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break;
case 'v1_p2tr': {
if (!vin.witness?.length) {
throw new Error('Taproot input missing witness data');
}
flags |= TransactionFlags.p2tr;
// in taproot, if the last witness item begins with 0x50, it's an annex
const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50');
// script spends have more than one witness item, not counting the annex (if present)
if (vin.witness.length > (hasAnnex ? 2 : 1)) {
// the script itself is the second-to-last witness item, not counting the annex
const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - (hasAnnex ? 3 : 2)]);
// inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope
if (asm?.includes('OP_0 OP_IF')) {
flags |= TransactionFlags.inscription;
if (vin.prevout?.scriptpubkey_type) {
switch (vin.prevout?.scriptpubkey_type) {
case 'p2pk': flags |= TransactionFlags.p2pk; break;
case 'multisig': flags |= TransactionFlags.p2ms; break;
case 'p2pkh': flags |= TransactionFlags.p2pkh; break;
case 'p2sh': flags |= TransactionFlags.p2sh; break;
case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break;
case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break;
case 'v1_p2tr': {
if (!vin.witness?.length) {
throw new Error('Taproot input missing witness data');
}
flags |= TransactionFlags.p2tr;
flags = Common.isInscription(vin, flags);
} break;
}
} else {
// no prevouts, optimistically check witness-bearing inputs
if (vin.witness?.length >= 2) {
try {
flags = Common.isInscription(vin, flags);
} catch {
// witness script parsing will fail if this isn't really a taproot output
}
} break;
}
}
// sighash flags
@@ -930,6 +946,33 @@ export class Common {
return this.validateTransactionHex(matches[1].toLowerCase());
}
static getTransactionsFromRequest(req: Request, limit: number = 25): string[] {
if (!Array.isArray(req.body) || req.body.some(hex => typeof hex !== 'string')) {
throw Object.assign(new Error('Invalid request body (should be an array of hexadecimal strings)'), { code: -1 });
}
if (limit && req.body.length > limit) {
throw Object.assign(new Error('Exceeded maximum of 25 transactions'), { code: -1 });
}
const txs = req.body;
return txs.map(rawTx => {
// Support both upper and lower case hex
// Support both txHash= Form and direct API POST
const reg = /^((?:[a-fA-F0-9]{2})+)$/;
const matches = reg.exec(rawTx);
if (!matches || !matches[1]) {
throw Object.assign(new Error('Invalid hex string'), { code: -2 });
}
// Guaranteed to be a hex string of multiple of 2
// Guaranteed to be lower case
// Guaranteed to pass validation (see function below)
return this.validateTransactionHex(matches[1].toLowerCase());
});
}
private static validateTransactionHex(txhex: string): string {
// Do not mutate txhex

View File

@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
import { RowDataPacket } from 'mysql2';
class DatabaseMigration {
private static currentVersion = 76;
private static currentVersion = 78;
private queryTimeout = 3600_000;
private statisticsAddedIndexed = false;
private uniqueLogs: string[] = [];
@@ -652,6 +652,11 @@ class DatabaseMigration {
await this.$executeQuery('ALTER TABLE `prices` ADD `THB` float DEFAULT "-1"');
await this.$executeQuery('ALTER TABLE `prices` ADD `TRY` float DEFAULT "-1"');
await this.$executeQuery('ALTER TABLE `prices` ADD `ZAR` float DEFAULT "-1"');
await this.$executeQuery('TRUNCATE hashrates');
await this.$executeQuery('TRUNCATE difficulty_adjustments');
await this.$executeQuery(`UPDATE state SET string = NULL WHERE name = 'pools_json_sha'`);
await this.updateToSchemaVersion(75);
}
@@ -659,6 +664,16 @@ class DatabaseMigration {
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"');
await this.updateToSchemaVersion(76);
}
if (databaseSchemaVersion < 77 && config.MEMPOOL.NETWORK === 'mainnet') {
await this.$executeQuery('ALTER TABLE `accelerations` ADD requested datetime DEFAULT NULL');
await this.updateToSchemaVersion(77);
}
if (databaseSchemaVersion < 78) {
await this.$executeQuery('ALTER TABLE `prices` CHANGE `time` `time` datetime NOT NULL');
await this.updateToSchemaVersion(78);
}
}
/**

View File

@@ -54,9 +54,11 @@ class ChannelsRoutes {
if (index < -1) {
res.status(400).send('Invalid index');
return;
}
if (['open', 'active', 'closed'].includes(status) === false) {
res.status(400).send('Invalid status');
return;
}
const channels = await channelsApi.$getChannelsForNode(req.query.public_key, index, 10, status);

View File

@@ -666,7 +666,9 @@ class NodesApi {
node.last_update = null;
}
const sockets = (node.addresses?.map(a => a.addr).join(',')) ?? '';
const uniqueAddr = [...new Set(node.addresses?.map(a => a.addr))];
const formattedSockets = (uniqueAddr.join(',')) ?? '';
const query = `INSERT INTO nodes(
public_key,
first_seen,
@@ -695,13 +697,13 @@ class NodesApi {
node.alias,
this.aliasToSearchText(node.alias),
node.color,
sockets,
formattedSockets,
JSON.stringify(node.features),
node.last_update,
node.alias,
this.aliasToSearchText(node.alias),
node.color,
sockets,
formattedSockets,
JSON.stringify(node.features),
]);
} catch (e) {
@@ -713,7 +715,9 @@ class NodesApi {
* Update node sockets
*/
public async $updateNodeSockets(publicKey: string, sockets: {network: string; addr: string}[]): Promise<void> {
const formattedSockets = (sockets.map(a => a.addr).join(',')) ?? '';
const uniqueAddr = [...new Set(sockets.map(a => a.addr))];
const formattedSockets = (uniqueAddr.join(',')) ?? '';
try {
await DB.query(`UPDATE nodes SET sockets = ? WHERE public_key = ?`, [formattedSockets, publicKey]);
} catch (e) {

View File

@@ -48,6 +48,14 @@ class NodesRoutes {
'032850492ee61a5f7006a2fda6925e4b4ec3782f2b6de2ff0e439ef5a38c3b2470',
'022c80bace98831c44c32fb69755f2b353434e0ee9e7fbda29507f7ef8abea1421',
'02c3559c833e6f99f9ca05fe503e0b4e7524dea9121344edfd3e811101e0c28680',
'02b36a324fa2dd3af2a63ac65f241907882829bed5002b4e14171d25c219e0d470',
'0231b6e8f21f9f6c057f6bf8a812f79e396ee16a66ece91939a1576ce9fb9e87a5',
'034b6aac206bffcbd651b7ead1ab8a0991c945dfafe19ff27dcdeadc6843ebd15c',
'039c065f7e344acd969ebdd4a94550915b6f24e8782ae2be540bb96c8a4fcfb86b',
'03d9f9f4803fc75920f14dd13d83fbecc53229a65d4ee4cd2d86fdf211f7337576',
'0357fe48c4dece744f70865eda66e396aab5d05e09e1145cd3b7da83f11446d4cf',
'02bca4d642eda631f2c8659758e2a2868e518b93503f2bfcd767749c6530a10679',
'03f32c99c0bb9f62dae53671d1d300565773455248f34134cc02779b881561174e',
'032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b',
'025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7',
'0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55',
@@ -60,6 +68,14 @@ class NodesRoutes {
'039c14fdec2d958e3d14cebf657451bbd9e039196615785e82c917f274e3fb2205',
'033589bbcb233ffc416cefd5437c7f37e9d7cb7942d405e39e72c4c846d9b37f18',
'029293110441c6e2eacb57e1255bf6ef05c41a6a676fe474922d33c19f98a7d584',
'038eb09bed4532ff36d12acc1279f55cbe8d95212d19f809e057bb50de00051fba',
'027b7c0278366a0268e8bd0072b14539f6cb455a7bd588ae22d888bed541f65311',
'02f4dd78f6eda8838029b2cdbaaea6e875e2fa373cd348ee41a7c1bb177d3fca66',
'036b3fb692da214a3edaac5b67903b958f5ccd8712e09aa61b67ea7acfd94b40c2',
'023bc8915d308e0b65f8de6867f95960141372436fce3edad5cec3f364d6ac948f',
'0341690503ef21d0e203dddd9e62646380d0dfc32c499e055e7f698b9064d1c736',
'0355d573805c018a37a5b2288378d70e9b5b438f7394abd6f467cb9b47c90eeb93',
'0361aa68deb561a8b47b41165848edcccb98a1b56a5ea922d9d5b30a09bb7282ea',
'0235ad0b56ed8c42c4354444c24e971c05e769ec0b5fb0ccea42880095dc02ea2c',
'029700819a37afea630f80e6cc461f3fd3c4ace2598a21cfbbe64d1c78d0ee69a5',
'02c2d8b2dbf87c7894af2f1d321290e2fe6db5446cd35323987cee98f06e2e0075',
@@ -76,6 +92,14 @@ class NodesRoutes {
'0243348cb3741cfe2d8485fa8375c29c7bc7cbb67577c363cb6987a5e5fd0052cc',
'02cb73e631af44bee600d80f8488a9194c9dc5c7590e575c421a070d1be05bc8e9',
'0306f55ee631aa1e2cd4d9b2bfcbc14404faec5c541cef8b2e6f779061029d09c4',
'030bbbd8495561a894e301fe6ba5b22f8941fc661cc0e673e0206158231d8ac130',
'03ee1f08e516ed083475f39c6cae4fa1eec686d004d2f105218269e27d7f2da5a4',
'028c378b998f476ed22d6815c170dd2a3388a43fdf791a7cff70b9997349b8447a',
'036f19f044d19cb1b04f14d91b6e7e5443ce337217a8c14d43861f3e86dd07bd7f',
'03058d61869e8b88436493648b2e3e530627edf5a0b253c285cd565c1477a5c237',
'0279dfedc87b47a941f1797f2c422c03aa3108914ea6b519d76537d60860535a9a',
'0353486b8016761e58ec8aee7305ee58d5dc66b55ef5bd8cbaf49508f66d52d62e',
'03df5db8eccfabcae47ff15553cfdecb2d3f56979f43a0c3578f28d056b5e35104',
'03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956',
'033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de',
'02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781',
@@ -88,6 +112,14 @@ class NodesRoutes {
'038310e3a786340f2bd7770704c7ccfe560fd163d9a1c99d67894597419d12cbf7',
'03e5e9d879b72c7d67ecd483bae023bd33e695bb32b981a4021260f7b9d62bc761',
'028d16e1a0ace4c0c0a421536d8d32ce484dfe6e2f726b7b0e7c30f12a195f8cc7',
'0326cf9a4ca67a5b9cdffae57293dbd6f7c5113b93010dc6f6fe4af3afde1a1739',
'034867e16f62cebb8c2c2c22b91117c173bbece9c8a1e5bd001374a3699551cd8f',
'038dfb1f1b637a8c27e342ffc6f9feca20e0b47be3244e09ae78df4998e2ae83b9',
'03cb1cea3394d973355c11bc61c2f689f9d3e1c3db60d205f27770f5ad83200f77',
'03535447b592cbdb153189b3e06a455452b1011380cb3e6511a31090c15d8efc9f',
'028e90e9984d262ebfa3c23fb3f335a2ae061a0bdedee03f45f72b438d9e7d2ce3',
'03ee0176289dc4a6111fa5ef22eed5273758c420fbe58cc1d2d76def75dd7e640c',
'0370b2cd9f0eaf436d5c25c93fb39210d8cc06b31f688fc2f54418aabe394aed79',
'02ff690d06c187ab994bf83c5a2114fe5bf50112c2c817af0f788f736be9fa2070',
'02a9f570c51a2526a5ee85802e88f9281bed771eb66a0c8a7d898430dd5d0eae45',
'038c3de773255d3bd7a50e31e58d423baac5c90826a74d75e64b74c95475de1097',
@@ -104,6 +136,14 @@ class NodesRoutes {
'03229ab4b7f692753e094b93df90530150680f86b535b5183b0cffd75b3df583fc',
'03a696eb7acde991c1be97a58a9daef416659539ae462b897f5e9ae361f990228e',
'0248bf26cf3a63ab8870f34dc0ec9e6c8c6288cdba96ba3f026f34ec0f13ac4055',
'021b28ecdd782fd909705d6be354db268977b1a2ac5a5275186fc19e08bb8fca93',
'031bec1fbd8eb7fe94d2bda108c9c3cc8c22ecfc1c3a5c11d36f5881b01b4a81a6',
'03879c4f827a3188574d5757e002f574265a966d70aea942169785b31369b067d5',
'0228d4b5a4fd73a03967b76f8b8cb37b9d0b6e7039126a9397bb732c15bed78e9b',
'03f58dbb629f4427f5a1dbc02e6a7ec79345fdf13a0e4163d4f3b7aea2539cf095',
'021cdcb8123aa670cdfc9f43909dbb297363c093883409e9e7fc82e7267f7c72bd',
'02f2aa2c2b7b432a70dc4d0b04afa19d48715ed3b90594d49c1c8744f2e9ebb030',
'03709a02fb3ab4857689a8ea0bd489a6ab6f56f8a397be578bc6d5ad22efbe3756',
'03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61',
'03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437',
'03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144',
@@ -116,6 +156,14 @@ class NodesRoutes {
'02b6b1640fe029e304c216951af9fbefdb23b0bdc9baaf327540d31b6107841fdf',
'03694289827203a5b3156d753071ddd5bf92e371f5a462943f9555eef6d2d6606c',
'0283d850db7c3e8ea7cc9c4abc7afaab12bbdf72b677dcba1d608350d2537d7d43',
'03b4dda7878d3b7b71ecd6d4738322c7f9a9c1fb583374d2724f4ccc4947f37570',
'0279a35f05b5acf159429549e56fd426685c4fec191431c58738968bbc77a39f25',
'03cb102d796ddcf08610cd03fae8b7a1df69ff48e9e8a152af315f9edf71762eb8',
'036b89526f4d5ac4c317f4fd23cb9f8e4ad844498bc7950a41114d060101d995d4',
'0313eade145959d7036db009fd5b0bf1947a739c7c3c790b491ec9161b94e6ad1e',
'02b670ca4c4bb2c5ea89c3b691da98a194cfc48fcd5c072df02a20290bddd60610',
'02a9196d5e08598211397a83cf013a5962b84bd61198abfdd204dff987e54f7a0d',
'036d015cd2f486fb38348182980b7e596e6c9733873102ea126fed7b4152be03b8',
'02521287789f851268a39c9eccc9d6180d2c614315b583c9e6ae0addbd6d79df06',
'0258c2a7b7f8af2585b4411b1ec945f70988f30412bb1df179de941f14d0b1bc3e',
'03c3389ff1a896f84d921ed01a19fc99c6724ce8dc4b960cd3b7b2362b62cd60d7',

View File

@@ -343,7 +343,7 @@ class MempoolBlocks {
if (txid in mempool) {
mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
mempool[txid].effectiveFeePerVsize = rate;
mempool[txid].cpfpChecked = false;
mempool[txid].cpfpChecked = true;
}
}

View File

@@ -404,6 +404,10 @@ class Mempool {
const newAccelerationMap: { [txid: string]: Acceleration } = {};
for (const acceleration of newAccelerations) {
// skip transactions we don't know about
if (!this.mempoolCache[acceleration.txid]) {
continue;
}
newAccelerationMap[acceleration.txid] = acceleration;
if (this.accelerations[acceleration.txid] == null) {
// new acceleration

View File

@@ -5,6 +5,9 @@ import axios from 'axios';
export interface Acceleration {
txid: string,
added: number,
effectiveVsize: number,
effectiveFee: number,
feeDelta: number,
pools: number[],
};

View File

@@ -3,6 +3,7 @@ import * as WebSocket from 'ws';
import {
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo,
MempoolBlockDelta, MempoolDelta, MempoolDeltaTxids
} from '../mempool.interfaces';
import blocks from './blocks';
import memPool from './mempool';
@@ -44,7 +45,7 @@ const wantable = [
];
class WebsocketHandler {
private wss: WebSocket.Server | undefined;
private webSocketServers: WebSocket.Server[] = [];
private extraInitProperties = {};
private numClients = 0;
@@ -54,11 +55,12 @@ class WebsocketHandler {
private socketData: { [key: string]: string } = {};
private serializedInitData: string = '{}';
private lastRbfSummary: ReplacementInfo[] | null = null;
private mempoolSequence: number = 0;
constructor() { }
setWebsocketServer(wss: WebSocket.Server) {
this.wss = wss;
addWebsocketServer(wss: WebSocket.Server) {
this.webSocketServers.push(wss);
}
setExtraInitData(property: string, value: any) {
@@ -102,11 +104,13 @@ class WebsocketHandler {
}
setupConnectionHandling() {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
this.wss.on('connection', (client: WebSocket, req) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.on('connection', (client: WebSocket, req) => {
this.numConnected++;
client['remoteAddress'] = req.headers['x-forwarded-for'] || req.socket?.remoteAddress || 'unknown';
client.on('error', (e) => {
@@ -315,6 +319,7 @@ class WebsocketHandler {
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
response['projected-block-transactions'] = JSON.stringify({
index: index,
sequence: this.mempoolSequence,
blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
});
} else {
@@ -342,6 +347,17 @@ class WebsocketHandler {
}
}
if (parsedMessage && parsedMessage['track-accelerations'] != null) {
if (parsedMessage['track-accelerations']) {
client['track-accelerations'] = true;
response['accelerations'] = JSON.stringify({
accelerations: Object.values(memPool.getAccelerations()),
});
} else {
client['track-accelerations'] = false;
}
}
if (parsedMessage.action === 'init') {
if (!this.socketData['blocks']?.length || !this.socketData['da'] || !this.socketData['backendInfo'] || !this.socketData['conversions']) {
this.updateSocketData();
@@ -360,12 +376,16 @@ class WebsocketHandler {
client['track-donation'] = parsedMessage['track-donation'];
}
if (parsedMessage['track-bisq-market']) {
if (/^[a-z]{3}_[a-z]{3}$/.test(parsedMessage['track-bisq-market'])) {
client['track-bisq-market'] = parsedMessage['track-bisq-market'];
} else {
client['track-bisq-market'] = null;
}
if (parsedMessage['track-mempool-txids'] === true) {
client['track-mempool-txids'] = true;
} else if (parsedMessage['track-mempool-txids'] === false) {
delete client['track-mempool-txids'];
}
if (parsedMessage['track-mempool'] === true) {
client['track-mempool'] = true;
} else if (parsedMessage['track-mempool'] === false) {
delete client['track-mempool'];
}
if (Object.keys(response).length) {
@@ -377,14 +397,17 @@ class WebsocketHandler {
}
});
});
}
}
handleNewDonation(id: string) {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
@@ -392,43 +415,50 @@ class WebsocketHandler {
client.send(JSON.stringify({ donationConfirmed: true }));
}
});
}
}
handleLoadingChanged(indicators: ILoadingIndicators) {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
this.updateSocketDataFields({ 'loadingIndicators': indicators });
const response = JSON.stringify({ loadingIndicators: indicators });
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
client.send(response);
});
}
}
handleNewConversionRates(conversionRates: ApiPrice) {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
this.updateSocketDataFields({ 'conversions': conversionRates });
const response = JSON.stringify({ conversions: conversionRates });
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
client.send(response);
});
}
}
handleNewStatistic(stats: OptimizedStatistic) {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
this.printLogs();
@@ -437,7 +467,9 @@ class WebsocketHandler {
'live-2h-chart': stats
});
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
@@ -448,11 +480,12 @@ class WebsocketHandler {
client.send(response);
});
}
}
handleReorg(): void {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
const da = difficultyAdjustment.getDifficultyAdjustment();
@@ -463,7 +496,9 @@ class WebsocketHandler {
'da': da?.previousTime ? da : undefined,
});
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
@@ -481,13 +516,14 @@ class WebsocketHandler {
client.send(this.serializeResponse(response));
}
});
}
}
async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number,
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[],
candidates?: GbtCandidates): Promise<void> {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
this.printLogs();
@@ -512,6 +548,7 @@ class WebsocketHandler {
const vBytesPerSecond = memPool.getVBytesPerSecond();
const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
const da = difficultyAdjustment.getDifficultyAdjustment();
const accelerations = memPool.getAccelerations();
memPool.handleRbfTransactions(rbfTransactions);
const rbfChanges = rbfCache.getRbfChanges();
let rbfReplacements;
@@ -533,6 +570,33 @@ class WebsocketHandler {
const latestTransactions = memPool.getLatestTransactions();
if (memPool.isInSync()) {
this.mempoolSequence++;
}
const replacedTransactions: { replaced: string, by: TransactionExtended }[] = [];
for (const tx of newTransactions) {
if (rbfTransactions[tx.txid]) {
for (const replaced of rbfTransactions[tx.txid]) {
replacedTransactions.push({ replaced: replaced.txid, by: tx });
}
}
}
const mempoolDeltaTxids: MempoolDeltaTxids = {
sequence: this.mempoolSequence,
added: newTransactions.map(tx => tx.txid),
removed: deletedTransactions.map(tx => tx.txid),
mined: [],
replaced: replacedTransactions.map(replacement => ({ replaced: replacement.replaced, by: replacement.by.txid })),
};
const mempoolDelta: MempoolDelta = {
sequence: this.mempoolSequence,
added: newTransactions,
removed: deletedTransactions.map(tx => tx.txid),
mined: [],
replaced: replacedTransactions,
};
// update init data
const socketDataFields = {
'mempoolInfo': mempoolInfo,
@@ -560,7 +624,9 @@ class WebsocketHandler {
// pre-compute new tracked outspends
const outspendCache: { [txid: string]: { [vout: number]: { vin: number, txid: string } } } = {};
const trackedTxs = new Set<string>();
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client['track-tx']) {
trackedTxs.add(client['track-tx']);
}
@@ -570,6 +636,7 @@ class WebsocketHandler {
}
}
});
}
if (trackedTxs.size > 0) {
for (const tx of newTransactions) {
for (let i = 0; i < tx.vin.length; i++) {
@@ -589,7 +656,15 @@ class WebsocketHandler {
const addressCache = this.makeAddressCache(newTransactions);
const removedAddressCache = this.makeAddressCache(deletedTransactions);
this.wss.clients.forEach(async (client) => {
// pre-compute acceleration delta
const accelerationUpdate = {
added: accelerationDelta.map(txid => accelerations[txid]).filter(acc => acc != null),
removed: accelerationDelta.filter(txid => !accelerations[txid]),
};
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach(async (client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
@@ -747,7 +822,7 @@ class WebsocketHandler {
accelerated: mempoolTx.acceleration || undefined,
}
};
if (!mempoolTx.cpfpChecked) {
if (!mempoolTx.cpfpChecked && !mempoolTx.acceleration) {
calculateCpfp(mempoolTx, newMempool);
}
if (mempoolTx.cpfpDirty) {
@@ -810,6 +885,7 @@ class WebsocketHandler {
if (mBlockDeltas[index]) {
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
index: index,
sequence: this.mempoolSequence,
delta: mBlockDeltas[index],
});
}
@@ -825,15 +901,28 @@ class WebsocketHandler {
response['rbfLatestSummary'] = getCachedResponse('rbfLatestSummary', rbfSummary);
}
if (client['track-mempool-txids']) {
response['mempool-txids'] = getCachedResponse('mempool-txids', mempoolDeltaTxids);
}
if (client['track-mempool']) {
response['mempool-transactions'] = getCachedResponse('mempool-transactions', mempoolDelta);
}
if (client['track-accelerations'] && (accelerationUpdate.added.length || accelerationUpdate.removed.length)) {
response['accelerations'] = getCachedResponse('accelerations', accelerationUpdate);
}
if (Object.keys(response).length) {
client.send(this.serializeResponse(response));
}
});
}
}
async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise<void> {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set');
}
this.printLogs();
@@ -969,6 +1058,31 @@ class WebsocketHandler {
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
if (memPool.isInSync()) {
this.mempoolSequence++;
}
const replacedTransactions: { replaced: string, by: TransactionExtended }[] = [];
for (const txid of Object.keys(rbfTransactions)) {
for (const replaced of rbfTransactions[txid].replaced) {
replacedTransactions.push({ replaced: replaced.txid, by: rbfTransactions[txid].replacedBy });
}
}
const mempoolDeltaTxids: MempoolDeltaTxids = {
sequence: this.mempoolSequence,
added: [],
removed: [],
mined: transactions.map(tx => tx.txid),
replaced: replacedTransactions.map(replacement => ({ replaced: replacement.replaced, by: replacement.by.txid })),
};
const mempoolDelta: MempoolDelta = {
sequence: this.mempoolSequence,
added: [],
removed: [],
mined: transactions.map(tx => tx.txid),
replaced: replacedTransactions,
};
const responseCache = { ...this.socketData };
function getCachedResponse(key, data): string {
if (!responseCache[key]) {
@@ -977,7 +1091,9 @@ class WebsocketHandler {
return responseCache[key];
}
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
@@ -1143,21 +1259,32 @@ class WebsocketHandler {
if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) {
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, {
index: index,
sequence: this.mempoolSequence,
blockTransactions: mBlocksWithTransactions[index].transactions.map(mempoolBlocks.compressTx),
});
} else {
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, {
index: index,
sequence: this.mempoolSequence,
delta: mBlockDeltas[index],
});
}
}
}
if (client['track-mempool-txids']) {
response['mempool-txids'] = getCachedResponse('mempool-txids', mempoolDeltaTxids);
}
if (client['track-mempool']) {
response['mempool-transactions'] = getCachedResponse('mempool-transactions', mempoolDelta);
}
if (Object.keys(response).length) {
client.send(this.serializeResponse(response));
}
});
}
await statistics.runStatistics();
}
@@ -1239,13 +1366,15 @@ class WebsocketHandler {
}
private printLogs(): void {
if (this.wss) {
if (this.webSocketServers.length) {
let numTxSubs = 0;
let numTxsSubs = 0;
let numProjectedSubs = 0;
let numRbfSubs = 0;
this.wss.clients.forEach((client) => {
// TODO - Fix indentation after PR is merged
for (const server of this.webSocketServers) {
server.clients.forEach((client) => {
if (client['track-tx']) {
numTxSubs++;
}
@@ -1259,8 +1388,12 @@ class WebsocketHandler {
numRbfSubs++;
}
})
}
const count = this.wss?.clients?.size || 0;
let count = 0;
for (const server of this.webSocketServers) {
count += server.clients?.size || 0;
}
const diff = count - this.numClients;
this.numClients = count;
logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`);

View File

@@ -9,6 +9,7 @@ interface IConfig {
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
BACKEND: 'esplora' | 'electrum' | 'none';
HTTP_PORT: number;
UNIX_SOCKET_PATH: string;
SPAWN_CLUSTER_PROCS: number;
API_URL_PREFIX: string;
POLL_RATE_MS: number;
@@ -116,10 +117,6 @@ interface IConfig {
ENABLED: boolean;
TX_PER_SECOND_SAMPLE_PERIOD: number;
};
BISQ: {
ENABLED: boolean;
DATA_PATH: string;
};
SOCKS5PROXY: {
ENABLED: boolean;
USE_ONION: boolean;
@@ -133,8 +130,6 @@ interface IConfig {
MEMPOOL_ONION: string;
LIQUID_API: string;
LIQUID_ONION: string;
BISQ_URL: string;
BISQ_ONION: string;
};
MAXMIND: {
ENABLED: boolean;
@@ -159,6 +154,7 @@ interface IConfig {
},
FIAT_PRICE: {
ENABLED: boolean;
PAID: boolean;
API_KEY: string;
},
}
@@ -170,6 +166,7 @@ const defaults: IConfig = {
'NETWORK': 'mainnet',
'BACKEND': 'none',
'HTTP_PORT': 8999,
'UNIX_SOCKET_PATH': '',
'SPAWN_CLUSTER_PROCS': 0,
'API_URL_PREFIX': '/api/v1/',
'POLL_RATE_MS': 2000,
@@ -258,10 +255,6 @@ const defaults: IConfig = {
'ENABLED': true,
'TX_PER_SECOND_SAMPLE_PERIOD': 150
},
'BISQ': {
'ENABLED': false,
'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db'
},
'LIGHTNING': {
'ENABLED': false,
'BACKEND': 'lnd',
@@ -293,9 +286,7 @@ const defaults: IConfig = {
'MEMPOOL_API': 'https://mempool.space/api/v1',
'MEMPOOL_ONION': 'http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/api/v1',
'LIQUID_API': 'https://liquid.network/api/v1',
'LIQUID_ONION': 'http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1',
'BISQ_URL': 'https://bisq.markets/api',
'BISQ_ONION': 'http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api'
'LIQUID_ONION': 'http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1'
},
'MAXMIND': {
'ENABLED': false,
@@ -320,6 +311,7 @@ const defaults: IConfig = {
},
'FIAT_PRICE': {
'ENABLED': true,
'PAID': false,
'API_KEY': '',
},
};
@@ -333,7 +325,6 @@ class Config implements IConfig {
DATABASE: IConfig['DATABASE'];
SYSLOG: IConfig['SYSLOG'];
STATISTICS: IConfig['STATISTICS'];
BISQ: IConfig['BISQ'];
LIGHTNING: IConfig['LIGHTNING'];
LND: IConfig['LND'];
CLIGHTNING: IConfig['CLIGHTNING'];
@@ -355,7 +346,6 @@ class Config implements IConfig {
this.DATABASE = configs.DATABASE;
this.SYSLOG = configs.SYSLOG;
this.STATISTICS = configs.STATISTICS;
this.BISQ = configs.BISQ;
this.LIGHTNING = configs.LIGHTNING;
this.LND = configs.LND;
this.CLIGHTNING = configs.CLIGHTNING;

View File

@@ -11,8 +11,6 @@ import memPool from './api/mempool';
import diskCache from './api/disk-cache';
import statistics from './api/statistics/statistics';
import websocketHandler from './api/websocket-handler';
import bisq from './api/bisq/bisq';
import bisqMarkets from './api/bisq/markets';
import logger from './logger';
import backendInfo from './api/backend-info';
import loadingIndicators from './api/loading-indicators';
@@ -32,7 +30,6 @@ import networkSyncService from './tasks/lightning/network-sync.service';
import statisticsRoutes from './api/statistics/statistics.routes';
import pricesRoutes from './api/prices/prices.routes';
import miningRoutes from './api/mining/mining-routes';
import bisqRoutes from './api/bisq/bisq.routes';
import liquidRoutes from './api/liquid/liquid.routes';
import bitcoinRoutes from './api/bitcoin/bitcoin.routes';
import fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher';
@@ -46,10 +43,14 @@ import redisCache from './api/redis-cache';
import accelerationApi from './api/services/acceleration';
import bitcoinCoreRoutes from './api/bitcoin/bitcoin-core.routes';
import bitcoinSecondClient from './api/bitcoin/bitcoin-second-client';
import accelerationRoutes from './api/acceleration/acceleration.routes';
import aboutRoutes from './api/about.routes';
class Server {
private wss: WebSocket.Server | undefined;
private wssUnixSocket: WebSocket.Server | undefined;
private server: http.Server | undefined;
private serverUnixSocket: http.Server | undefined;
private app: Application;
private currentBackendRetryInterval = 1;
private backendRetryCount = 0;
@@ -130,6 +131,7 @@ class Server {
})
.use(express.urlencoded({ extended: true }))
.use(express.text({ type: ['text/plain', 'application/base64'] }))
.use(express.json())
;
if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) {
@@ -138,6 +140,10 @@ class Server {
this.server = http.createServer(this.app);
this.wss = new WebSocket.Server({ server: this.server });
if (config.MEMPOOL.UNIX_SOCKET_PATH) {
this.serverUnixSocket = http.createServer(this.app);
this.wssUnixSocket = new WebSocket.Server({ server: this.serverUnixSocket });
}
this.setUpWebsocketHandling();
@@ -182,13 +188,6 @@ class Server {
setInterval(() => { this.healthCheck(); }, 2500);
if (config.BISQ.ENABLED) {
bisq.startBisqService();
bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitData('bsq-price', price));
blocks.setNewBlockCallback(bisq.handleNewBitcoinBlock.bind(bisq));
bisqMarkets.startBisqService();
}
if (config.LIGHTNING.ENABLED) {
this.$runLightningBackend();
}
@@ -200,6 +199,16 @@ class Server {
logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`);
}
});
if (this.serverUnixSocket) {
this.serverUnixSocket.listen(config.MEMPOOL.UNIX_SOCKET_PATH, () => {
if (worker) {
logger.info(`Mempool Server worker #${process.pid} started`);
} else {
logger.notice(`Mempool Server is listening on ${config.MEMPOOL.UNIX_SOCKET_PATH}`);
}
});
}
}
async runMainUpdateLoop(): Promise<void> {
@@ -273,8 +282,12 @@ class Server {
setUpWebsocketHandling(): void {
if (this.wss) {
websocketHandler.setWebsocketServer(this.wss);
websocketHandler.addWebsocketServer(this.wss);
}
if (this.wssUnixSocket) {
websocketHandler.addWebsocketServer(this.wssUnixSocket);
}
if (Common.isLiquid() && config.DATABASE.ENABLED) {
blocks.setNewBlockCallback(async () => {
try {
@@ -307,9 +320,6 @@ class Server {
if (Common.indexingEnabled() && config.MEMPOOL.ENABLED) {
miningRoutes.initRoutes(this.app);
}
if (config.BISQ.ENABLED) {
bisqRoutes.initRoutes(this.app);
}
if (Common.isLiquid()) {
liquidRoutes.initRoutes(this.app);
}
@@ -318,6 +328,10 @@ class Server {
nodesRoutes.initRoutes(this.app);
channelsRoutes.initRoutes(this.app);
}
if (config.MEMPOOL_SERVICES.ACCELERATIONS) {
accelerationRoutes.initRoutes(this.app);
}
aboutRoutes.initRoutes(this.app);
}
healthCheck(): void {
@@ -345,6 +359,12 @@ class Server {
if (config.DATABASE.ENABLED) {
DB.releasePidLock();
}
this.server?.close();
this.serverUnixSocket?.close();
this.wss?.close();
if (this.wssUnixSocket) {
this.wssUnixSocket.close();
}
process.exit(code);
}

View File

@@ -86,9 +86,6 @@ class Logger {
if (config.LIGHTNING.ENABLED) {
return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`;
}
if (config.BISQ.ENABLED) {
return 'bisq';
}
if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') {
return config.MEMPOOL.NETWORK;
}

View File

@@ -71,6 +71,22 @@ export interface MempoolBlockDelta {
changed: MempoolDeltaChange[];
}
export interface MempoolDeltaTxids {
sequence: number,
added: string[];
removed: string[];
mined: string[];
replaced: { replaced: string, by: string }[];
}
export interface MempoolDelta {
sequence: number,
added: MempoolTransactionExtended[];
removed: string[];
mined: string[];
replaced: { replaced: string, by: TransactionExtended }[];
}
interface VinStrippedToScriptsig {
scriptsig: string;
}
@@ -435,7 +451,6 @@ export interface WebsocketResponse {
'track-tx': string;
'track-address': string;
'watch-mempool': boolean;
'track-bisq-market': string;
}
export interface VbytesPerSecond {

View File

@@ -1,4 +1,4 @@
import { AccelerationInfo, makeBlockTemplate } from '../api/acceleration';
import { AccelerationInfo, makeBlockTemplate } from '../api/acceleration/acceleration';
import { RowDataPacket } from 'mysql2';
import DB from '../database';
import logger from '../logger';
@@ -6,8 +6,8 @@ import { IEsploraApi } from '../api/bitcoin/esplora-api.interface';
import { Common } from '../api/common';
import config from '../config';
import blocks from '../api/blocks';
import accelerationApi, { Acceleration } from '../api/services/acceleration';
import accelerationCosts from '../api/acceleration';
import accelerationApi, { Acceleration, AccelerationHistory } from '../api/services/acceleration';
import accelerationCosts from '../api/acceleration/acceleration';
import bitcoinApi from '../api/bitcoin/bitcoin-api-factory';
import transactionUtils from '../api/transaction-utils';
import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces';
@@ -15,6 +15,7 @@ import { BlockExtended, MempoolTransactionExtended } from '../mempool.interfaces
export interface PublicAcceleration {
txid: string,
height: number,
added: number,
pool: {
id: number,
slug: string,
@@ -29,15 +30,20 @@ export interface PublicAcceleration {
class AccelerationRepository {
private bidBoostV2Activated = 831580;
public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number): Promise<void> {
public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number, accelerationData: Acceleration[]): Promise<void> {
const accelerationMap: { [txid: string]: Acceleration } = {};
for (const acc of accelerationData) {
accelerationMap[acc.txid] = acc;
}
try {
await DB.query(`
INSERT INTO accelerations(txid, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost)
VALUE (?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)
INSERT INTO accelerations(txid, requested, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost)
VALUE (?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
height = ?
`, [
acceleration.txSummary.txid,
accelerationMap[acceleration.txSummary.txid].added,
block.timestamp,
block.height,
pool_id,
@@ -64,7 +70,7 @@ class AccelerationRepository {
}
let query = `
SELECT * FROM accelerations
SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations
JOIN pools on pools.unique_id = accelerations.pool
`;
let params: any[] = [];
@@ -99,6 +105,7 @@ class AccelerationRepository {
return rows.map(row => ({
txid: row.txid,
height: row.height,
added: row.requested_timestamp || row.block_timestamp,
pool: {
id: row.id,
slug: row.slug,
@@ -202,7 +209,7 @@ class AccelerationRepository {
const tx = blockTxs[acc.txid];
const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions);
accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost));
this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id);
this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, successfulAccelerations);
}
}
const lastSyncedHeight = await this.$getLastSyncedHeight();
@@ -230,7 +237,7 @@ class AccelerationRepository {
logger.debug(`Fetching accelerations between block ${lastSyncedHeight} and ${currentHeight}`);
// Fetch accelerations from mempool.space since the last synced block;
const accelerationsByBlock = {};
const accelerationsByBlock: {[height: number]: AccelerationHistory[]} = {};
const blockHashes = {};
let done = false;
let page = 1;
@@ -297,12 +304,16 @@ class AccelerationRepository {
const feeStats = Common.calcEffectiveFeeStatistics(template);
boostRate = feeStats.medianFee;
}
const accelerationSummaries = accelerations.map(acc => ({
...acc,
pools: acc.pools.map(pool => pool.pool_unique_id),
}))
for (const acc of accelerations) {
if (blockTxs[acc.txid]) {
const tx = blockTxs[acc.txid];
const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions);
accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost));
await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id);
await this.$saveAcceleration(accelerationInfo, block, block.extras.pool.id, accelerationSummaries);
}
}
await this.$setLastSyncedHeight(height);
@@ -317,6 +328,26 @@ class AccelerationRepository {
logger.debug(`Indexing accelerations completed`);
}
/**
* Delete accelerations from the database above blockHeight
*/
public async $deleteAccelerationsFrom(blockHeight: number): Promise<void> {
logger.info(`Delete newer accelerations from height ${blockHeight} from the database`);
try {
const currentSyncedHeight = await this.$getLastSyncedHeight();
if (currentSyncedHeight >= blockHeight) {
await DB.query(`
UPDATE state
SET number = ?
WHERE name = 'last_acceleration_block'
`, [blockHeight - 1]);
}
await DB.query(`DELETE FROM accelerations where height >= ${blockHeight}`);
} catch (e) {
logger.err('Cannot delete indexed accelerations. Reason: ' + (e instanceof Error ? e.message : e));
}
}
}
export default new AccelerationRepository();

View File

@@ -1,3 +1,4 @@
import config from '../../config';
import { query } from '../../utils/axios-query';
import { ConversionFeed, ConversionRates } from '../price-updater';
@@ -37,15 +38,26 @@ const emptyRates = {
ZAR: -1,
};
class FreeCurrencyApi implements ConversionFeed {
private API_KEY: string;
constructor(apiKey: string) {
this.API_KEY = apiKey;
type PaidCurrencyData = {
[key: string]: {
code: string;
value: number;
}
};
type FreeCurrencyData = {
[key: string]: number;
};
class FreeCurrencyApi implements ConversionFeed {
private API_KEY = config.FIAT_PRICE.API_KEY;
private PAID = config.FIAT_PRICE.PAID;
private API_URL_PREFIX: string = this.PAID ? `https://api.currencyapi.com/v3/` : `https://api.freecurrencyapi.com/v1/`;
constructor() { }
public async $getQuota(): Promise<any> {
const response = await query(`https://api.freecurrencyapi.com/v1/status?apikey=${this.API_KEY}`);
const response = await query(`${this.API_URL_PREFIX}status?apikey=${this.API_KEY}`);
if (response && response['quotas']) {
return response['quotas'];
}
@@ -53,21 +65,36 @@ class FreeCurrencyApi implements ConversionFeed {
}
public async $fetchLatestConversionRates(): Promise<ConversionRates> {
const response = await query(`https://api.freecurrencyapi.com/v1/latest?apikey=${this.API_KEY}`);
const response = await query(`${this.API_URL_PREFIX}latest?apikey=${this.API_KEY}`);
if (response && response['data']) {
if (this.PAID) {
response['data'] = this.convertData(response['data']);
}
return response['data'];
}
return emptyRates;
}
public async $fetchConversionRates(date: string): Promise<ConversionRates> {
const response = await query(`https://api.freecurrencyapi.com/v1/historical?date=${date}&apikey=${this.API_KEY}`);
if (response && response['data'] && response['data'][date]) {
const response = await query(`${this.API_URL_PREFIX}historical?date=${date}&apikey=${this.API_KEY}`);
if (response && response['data'] && (response['data'][date] || this.PAID)) {
if (this.PAID) {
response['data'] = this.convertData(response['data']);
response['data'][response['meta'].last_updated_at.substr(0, 10)] = response['data'];
}
return response['data'][date];
}
return emptyRates;
}
private convertData(data: PaidCurrencyData): FreeCurrencyData {
const simplifiedData: FreeCurrencyData = {};
for (const key in data) {
simplifiedData[key] = data[key].value;
}
return simplifiedData;
}
}
export default FreeCurrencyApi;

View File

@@ -71,7 +71,7 @@ class PriceUpdater {
this.feeds.push(new BitfinexApi());
this.feeds.push(new GeminiApi());
this.currencyConversionFeed = new FreeCurrencyApi(config.FIAT_PRICE.API_KEY);
this.currencyConversionFeed = new FreeCurrencyApi();
this.setCyclePosition();
}

3
contributors/daweilv.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 April 7, 2024.
Signed: daweilv

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 April 12, 2024.
Signed: henrialb

View File

@@ -323,25 +323,6 @@ Corresponding `docker-compose.yml` overrides:
<br/>
`mempool-config.json`:
```json
"BISQ": {
"ENABLED": false,
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
}
```
Corresponding `docker-compose.yml` overrides:
```yaml
api:
environment:
BISQ_ENABLED: ""
BISQ_DATA_PATH: ""
...
```
<br/>
`mempool-config.json`:
```json
"SOCKS5PROXY": {

View File

@@ -11,10 +11,17 @@ RUN apt-get install -y build-essential python3 pkg-config curl ca-certificates
# Install Rust via rustup
RUN CPU_ARCH=$(uname -m); if [ "$CPU_ARCH" = "armv7l" ]; then c_rehash; fi
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
#RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
#Workaround to run on github actions from https://github.com/rust-lang/rustup/issues/2700#issuecomment-1367488985
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sed 's#/proc/self/exe#\/bin\/sh#g' | sh -s -- -y --default-toolchain stable
ENV PATH="/root/.cargo/bin:$PATH"
COPY --from=backend . .
COPY --from=rustgbt . ../rust/
ENV FD=/build/rust-gbt
RUN npm install --omit=dev --omit=optional
WORKDIR /build
RUN npm run package
FROM node:20.12.0-buster-slim

View File

@@ -6,6 +6,7 @@
"OFFICIAL": __MEMPOOL_OFFICIAL__,
"HTTP_PORT": __MEMPOOL_HTTP_PORT__,
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
"UNIX_SOCKET_PATH": "__MEMPOOL_UNIX_SOCKET_PATH__",
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
"POLL_RATE_MS": __MEMPOOL_POLL_RATE_MS__,
"CACHE_DIR": "__MEMPOOL_CACHE_DIR__",
@@ -93,10 +94,6 @@
"ENABLED": __STATISTICS_ENABLED__,
"TX_PER_SECOND_SAMPLE_PERIOD": __STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__
},
"BISQ": {
"ENABLED": __BISQ_ENABLED__,
"DATA_PATH": "__BISQ_DATA_PATH__"
},
"LIGHTNING": {
"ENABLED": __LIGHTNING_ENABLED__,
"BACKEND": "__LIGHTNING_BACKEND__",
@@ -128,9 +125,7 @@
"MEMPOOL_API": "__EXTERNAL_DATA_SERVER_MEMPOOL_API__",
"MEMPOOL_ONION": "__EXTERNAL_DATA_SERVER_MEMPOOL_ONION__",
"LIQUID_API": "__EXTERNAL_DATA_SERVER_LIQUID_API__",
"LIQUID_ONION": "__EXTERNAL_DATA_SERVER_LIQUID_ONION__",
"BISQ_URL": "__EXTERNAL_DATA_SERVER_BISQ_URL__",
"BISQ_ONION": "__EXTERNAL_DATA_SERVER_BISQ_ONION__"
"LIQUID_ONION": "__EXTERNAL_DATA_SERVER_LIQUID_ONION__"
},
"MAXMIND": {
"ENABLED": __MAXMIND_ENABLED__,
@@ -155,6 +150,7 @@
},
"FIAT_PRICE": {
"ENABLED": __FIAT_PRICE_ENABLED__,
"PAID": __FIAT_PRICE_PAID__,
"API_KEY": "__FIAT_PRICE_API_KEY__"
}
}

View File

@@ -7,6 +7,7 @@ __MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=true}
__MEMPOOL_OFFICIAL__=${MEMPOOL_OFFICIAL:=false}
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
__MEMPOOL_UNIX_SOCKET_PATH__=${MEMPOOL_UNIX_SOCKET_PATH:=""}
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
__MEMPOOL_POLL_RATE_MS__=${MEMPOOL_POLL_RATE_MS:=2000}
__MEMPOOL_CACHE_DIR__=${MEMPOOL_CACHE_DIR:=./cache}
@@ -94,10 +95,6 @@ __SYSLOG_FACILITY__=${SYSLOG_FACILITY:=local7}
__STATISTICS_ENABLED__=${STATISTICS_ENABLED:=true}
__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__=${STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD:=150}
# BISQ
__BISQ_ENABLED__=${BISQ_ENABLED:=false}
__BISQ_DATA_PATH__=${BISQ_DATA_PATH:=/bisq/statsnode-data/btc_mainnet/db}
# SOCKS5PROXY
__SOCKS5PROXY_ENABLED__=${SOCKS5PROXY_ENABLED:=false}
__SOCKS5PROXY_USE_ONION__=${SOCKS5PROXY_USE_ONION:=true}
@@ -111,8 +108,6 @@ __EXTERNAL_DATA_SERVER_MEMPOOL_API__=${EXTERNAL_DATA_SERVER_MEMPOOL_API:=https:/
__EXTERNAL_DATA_SERVER_MEMPOOL_ONION__=${EXTERNAL_DATA_SERVER_MEMPOOL_ONION:=http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/api/v1}
__EXTERNAL_DATA_SERVER_LIQUID_API__=${EXTERNAL_DATA_SERVER_LIQUID_API:=https://liquid.network/api/v1}
__EXTERNAL_DATA_SERVER_LIQUID_ONION__=${EXTERNAL_DATA_SERVER_LIQUID_ONION:=http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1}
__EXTERNAL_DATA_SERVER_BISQ_URL__=${EXTERNAL_DATA_SERVER_BISQ_URL:=https://bisq.markets/api}
__EXTERNAL_DATA_SERVER_BISQ_ONION__=${EXTERNAL_DATA_SERVER_BISQ_ONION:=http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api}
# LIGHTNING
__LIGHTNING_ENABLED__=${LIGHTNING_ENABLED:=false}
@@ -140,8 +135,8 @@ __MAXMIND_GEOLITE2_ASN__=${MAXMIND_GEOLITE2_ASN:="/backend/GeoIP/GeoLite2-ASN.mm
__MAXMIND_GEOIP2_ISP__=${MAXMIND_GEOIP2_ISP:=""}
# REPLICATION
__REPLICATION_ENABLED__=${REPLICATION_ENABLED:=true}
__REPLICATION_AUDIT__=${REPLICATION_AUDIT:=true}
__REPLICATION_ENABLED__=${REPLICATION_ENABLED:=false}
__REPLICATION_AUDIT__=${REPLICATION_AUDIT:=false}
__REPLICATION_AUDIT_START_HEIGHT__=${REPLICATION_AUDIT_START_HEIGHT:=774000}
__REPLICATION_SERVERS__=${REPLICATION_SERVERS:=[]}
@@ -156,6 +151,7 @@ __REDIS_BATCH_QUERY_BASE_SIZE__=${REDIS_BATCH_QUERY_BASE_SIZE:=5000}
# FIAT_PRICE
__FIAT_PRICE_ENABLED__=${FIAT_PRICE_ENABLED:=true}
__FIAT_PRICE_PAID__=${FIAT_PRICE_PAID:=false}
__FIAT_PRICE_API_KEY__=${FIAT_PRICE_API_KEY:=""}
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
@@ -166,6 +162,7 @@ sed -i "s!__MEMPOOL_ENABLED__!${__MEMPOOL_ENABLED__}!g" mempool-config.json
sed -i "s!__MEMPOOL_OFFICIAL__!${__MEMPOOL_OFFICIAL__}!g" mempool-config.json
sed -i "s!__MEMPOOL_HTTP_PORT__!${__MEMPOOL_HTTP_PORT__}!g" mempool-config.json
sed -i "s!__MEMPOOL_SPAWN_CLUSTER_PROCS__!${__MEMPOOL_SPAWN_CLUSTER_PROCS__}!g" mempool-config.json
sed -i "s!__MEMPOOL_UNIX_SOCKET_PATH__!${__MEMPOOL_UNIX_SOCKET_PATH__}!g" mempool-config.json
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json
sed -i "s!__MEMPOOL_POLL_RATE_MS__!${__MEMPOOL_POLL_RATE_MS__}!g" mempool-config.json
sed -i "s!__MEMPOOL_CACHE_DIR__!${__MEMPOOL_CACHE_DIR__}!g" mempool-config.json
@@ -246,9 +243,6 @@ sed -i "s!__SYSLOG_FACILITY__!${__SYSLOG_FACILITY__}!g" mempool-config.json
sed -i "s!__STATISTICS_ENABLED__!${__STATISTICS_ENABLED__}!g" mempool-config.json
sed -i "s!__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__!${__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__}!g" mempool-config.json
sed -i "s!__BISQ_ENABLED__!${__BISQ_ENABLED__}!g" mempool-config.json
sed -i "s!__BISQ_DATA_PATH__!${__BISQ_DATA_PATH__}!g" mempool-config.json
sed -i "s!__SOCKS5PROXY_ENABLED__!${__SOCKS5PROXY_ENABLED__}!g" mempool-config.json
sed -i "s!__SOCKS5PROXY_USE_ONION__!${__SOCKS5PROXY_USE_ONION__}!g" mempool-config.json
sed -i "s!__SOCKS5PROXY_HOST__!${__SOCKS5PROXY_HOST__}!g" mempool-config.json
@@ -260,8 +254,6 @@ sed -i "s!__EXTERNAL_DATA_SERVER_MEMPOOL_API__!${__EXTERNAL_DATA_SERVER_MEMPOOL_
sed -i "s!__EXTERNAL_DATA_SERVER_MEMPOOL_ONION__!${__EXTERNAL_DATA_SERVER_MEMPOOL_ONION__}!g" mempool-config.json
sed -i "s!__EXTERNAL_DATA_SERVER_LIQUID_API__!${__EXTERNAL_DATA_SERVER_LIQUID_API__}!g" mempool-config.json
sed -i "s!__EXTERNAL_DATA_SERVER_LIQUID_ONION__!${__EXTERNAL_DATA_SERVER_LIQUID_ONION__}!g" mempool-config.json
sed -i "s!__EXTERNAL_DATA_SERVER_BISQ_URL__!${__EXTERNAL_DATA_SERVER_BISQ_URL__}!g" mempool-config.json
sed -i "s!__EXTERNAL_DATA_SERVER_BISQ_ONION__!${__EXTERNAL_DATA_SERVER_BISQ_ONION__}!g" mempool-config.json
# LIGHTNING
sed -i "s!__LIGHTNING_ENABLED__!${__LIGHTNING_ENABLED__}!g" mempool-config.json
@@ -305,6 +297,7 @@ sed -i "s!__REDIS_BATCH_QUERY_BASE_SIZE__!${__REDIS_BATCH_QUERY_BASE_SIZE__}!g"
# FIAT_PRICE
sed -i "s!__FIAT_PRICE_ENABLED__!${__FIAT_PRICE_ENABLED__}!g" mempool-config.json
sed -i "s!__FIAT_PRICE_PAID__!${__FIAT_PRICE_PAID__}!g" mempool-config.json
sed -i "s!__FIAT_PRICE_API_KEY__!${__FIAT_PRICE_API_KEY__}!g" mempool-config.json
node /backend/package/index.js

View File

@@ -13,7 +13,7 @@ RUN npm install --omit=dev --omit=optional
RUN npm run build
FROM nginx:1.25.4-alpine
FROM nginx:1.26.0-alpine
WORKDIR /patch

View File

@@ -20,8 +20,6 @@ __TESTNET_ENABLED__=${TESTNET_ENABLED:=false}
__SIGNET_ENABLED__=${SIGNET_ENABLED:=false}
__LIQUID_ENABLED__=${LIQUID_ENABLED:=false}
__LIQUID_TESTNET_ENABLED__=${LIQUID_TESTNET_ENABLED:=false}
__BISQ_ENABLED__=${BISQ_ENABLED:=false}
__BISQ_SEPARATE_BACKEND__=${BISQ_SEPARATE_BACKEND:=false}
__ITEMS_PER_PAGE__=${ITEMS_PER_PAGE:=10}
__KEEP_BLOCKS_AMOUNT__=${KEEP_BLOCKS_AMOUNT:=8}
__NGINX_PROTOCOL__=${NGINX_PROTOCOL:=http}
@@ -32,7 +30,6 @@ __MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_BLOCKS_AMOUNT:=8}
__BASE_MODULE__=${BASE_MODULE:=mempool}
__MEMPOOL_WEBSITE_URL__=${MEMPOOL_WEBSITE_URL:=https://mempool.space}
__LIQUID_WEBSITE_URL__=${LIQUID_WEBSITE_URL:=https://liquid.network}
__BISQ_WEBSITE_URL__=${BISQ_WEBSITE_URL:=https://bisq.markets}
__MINING_DASHBOARD__=${MINING_DASHBOARD:=true}
__LIGHTNING__=${LIGHTNING:=false}
__AUDIT__=${AUDIT:=false}
@@ -40,6 +37,7 @@ __MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
__TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0}
__SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0}
__ACCELERATOR__=${ACCELERATOR:=false}
__PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false}
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
__ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
@@ -48,8 +46,6 @@ export __TESTNET_ENABLED__
export __SIGNET_ENABLED__
export __LIQUID_ENABLED__
export __LIQUID_TESTNET_ENABLED__
export __BISQ_ENABLED__
export __BISQ_SEPARATE_BACKEND__
export __ITEMS_PER_PAGE__
export __KEEP_BLOCKS_AMOUNT__
export __NGINX_PROTOCOL__
@@ -60,7 +56,6 @@ export __MEMPOOL_BLOCKS_AMOUNT__
export __BASE_MODULE__
export __MEMPOOL_WEBSITE_URL__
export __LIQUID_WEBSITE_URL__
export __BISQ_WEBSITE_URL__
export __MINING_DASHBOARD__
export __LIGHTNING__
export __AUDIT__
@@ -68,6 +63,7 @@ export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
export __ACCELERATOR__
export __PUBLIC_ACCELERATIONS__
export __HISTORICAL_PRICE__
export __ADDITIONAL_CURRENCIES__

1
frontend/.gitignore vendored
View File

@@ -63,6 +63,7 @@ src/resources/pools.json
src/resources/mining-pools/*
src/resources/**/*.mp4
src/resources/**/*.vtt
src/resources/customize.js
# environment config
mempool-frontend-config.json

View File

@@ -22,14 +22,13 @@ cd mempool/frontend
### 2. Specify Website
The same frontend codebase is used for https://mempool.space, https://liquid.network and https://bisq.markets.
The same frontend codebase is used for https://mempool.space and https://liquid.network.
Configure the frontend for the site you want by running the corresponding command:
```
$ npm run config:defaults:mempool
$ npm run config:defaults:liquid
$ npm run config:defaults:bisq
```
### 3. Run the Frontend

View File

@@ -166,10 +166,26 @@
"src/resources",
"src/robots.txt",
"src/config.js",
"src/customize.js",
"src/config.template.js"
],
"styles": [
"src/styles.scss",
{
"input": "src/theme-contrast.scss",
"bundleName": "contrast",
"inject": false
},
{
"input": "src/theme-wiz.scss",
"bundleName": "wiz",
"inject": false
},
{
"input": "src/theme-bukele.scss",
"bundleName": "bukele",
"inject": false
},
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
],
"vendorChunk": true,

View File

@@ -0,0 +1,52 @@
{
"theme": "bukele",
"enterprise": "onbtc",
"branding": {
"name": "onbtc",
"title": "Bitcoin Office",
"site_id": 19,
"header_img": "/resources/onbtclogo.svg",
"footer_img": "/resources/onbtclogo.svg",
"rounded_corner": true
},
"dashboard": {
"widgets": [
{
"component": "fees",
"mobileOrder": 4
},
{
"component": "balance",
"mobileOrder": 1,
"props": {
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
}
},
{
"component": "twitter",
"mobileOrder": 5,
"props": {
"handle": "nayibbukele"
}
},
{
"component": "address",
"mobileOrder": 2,
"props": {
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo",
"period": "1m"
}
},
{
"component": "blocks"
},
{
"component": "addressTransactions",
"mobileOrder": 3,
"props": {
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
}
}
]
}
}

View File

@@ -1,164 +0,0 @@
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');
});
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);
});
});
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.only(`filters the transaction screen by ${filter}`, () => {
cy.visit(`${basePath}/transactions`);
cy.wait('@txs');
cy.waitForSkeletonGone();
cy.get('#filter').click();
cy.contains(filter).find('input').click();
cy.wait('@txs');
cy.wait(500);
cy.get('td:nth-of-type(2)').each(($td) => {
expect($td.text().trim()).to.eq(filter);
});
});
});
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(1500);
});
cy.get('td:nth-of-type(2)').each(($td) => {
const regex = new RegExp(`${filters.join('|')}`, 'g');
expect($td.text().trim()).to.match(regex);
});
});
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' }
];
transactions.forEach((transaction) => {
it(`loads a "${transaction.type}" transaction`, () => {
cy.visit(`${basePath}/tx/${transaction.txid}`);
cy.waitForSkeletonGone();
});
});
});
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('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,4 +1,4 @@
describe.skip('Liquid', () => {
describe('Liquid', () => {
const baseModule = Cypress.env('BASE_MODULE');
const basePath = '';
@@ -23,6 +23,13 @@ describe.skip('Liquid', () => {
cy.get('#mempool-block-0 > .blockLink').should('exist');
});
it('load first mempool block after skeleton loads', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('#mempool-block-0 > .blockLink').click();
cy.waitForSkeletonGone();
});
it('loads the dashboard', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
@@ -84,10 +91,11 @@ describe.skip('Liquid', () => {
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(() => {
//Remove the target=_blank attribute so the new url opens in the same tab
cy.get('.table-tx-vin a').invoke('removeAttr', 'target').click().then(() => {
cy.waitForSkeletonGone();
if (baseModule === 'liquid') {
cy.url().should('eq', 'https://mempool.space/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592');
cy.url().should('eq', 'https://mempool.space/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592#vout=0');
} else {
//TODO: Use an environment variable to get the hostname
cy.url().should('eq', 'http://localhost:4200/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592');
@@ -98,7 +106,8 @@ describe.skip('Liquid', () => {
it('loads peg out addresses', () => {
cy.visit(`${basePath}/tx/ecf6eba04ffb3946faa172343c87162df76f1a57b07b0d6dc6ad956b13376dc8`);
cy.waitForSkeletonGone();
cy.get('.table-tx-vout a').first().click().then(() => {
//Remove the target=_blank attribute so the new url opens in the same tab
cy.get('.table-tx-vout a').first().invoke('removeAttr', 'target').click().then(() => {
cy.waitForSkeletonGone();
if (baseModule === 'liquid') {
cy.url().should('eq', 'https://mempool.space/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR');

View File

@@ -1,4 +1,4 @@
describe.skip('Liquid Testnet', () => {
describe('Liquid Testnet', () => {
const baseModule = Cypress.env('BASE_MODULE');
const basePath = '/testnet';
@@ -28,6 +28,17 @@ describe.skip('Liquid Testnet', () => {
cy.waitForSkeletonGone();
});
it.skip('loads the dashboard with no scrollbars on mobile', () => {
cy.viewport('iphone-xr');
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.window().then(window => {
const htmlWidth = Cypress.$('html')[0].scrollWidth;
const scrollBarWidth = window.innerWidth - htmlWidth;
expect(scrollBarWidth).to.be.eq(0); //check for no horizontal scrollbar
});
});
it('loads the blocks page', () => {
cy.visit(`${basePath}`)
cy.get('#btn-blocks');
@@ -57,17 +68,14 @@ describe.skip('Liquid Testnet', () => {
cy.get('.tv-only').should('not.exist');
});
it.skip('renders unconfidential addresses correctly on mobile', () => {
cy.viewport('iphone-6');
cy.visit(`${basePath}/address/__TODO__`);
it.skip('renders unconfidential transactions correctly on mobile', () => {
cy.viewport('iphone-xr');
cy.visit(`${basePath}/tx/b119f338878416781dc285b94c0de52826341dea43566e4de4740d3ebfd1f6dc#blinded=99707,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,1377e4ec8eb0c89296e14ffca57e377f4b51ad8f1c881e43364434d8430dbfda,cdd6caae4c3452586cfcb107478dd2b7acaa5f82714a6a966578255e857eee60`);
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));
});
cy.window().then(window => {
const htmlWidth = Cypress.$('html')[0].scrollWidth;
const scrollBarWidth = window.innerWidth - htmlWidth;
expect(scrollBarWidth).to.be.eq(0); //check for no horizontal scrollbar
});
});

View File

@@ -67,7 +67,7 @@ describe('Mainnet', () => {
cy.get('[id^="bitcoin-block-"]').should('have.length', 22);
cy.get('.footer').should('be.visible');
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
expect(text).to.match(/Incoming transactions.* vB\/s/);
expect(text).to.match(/Incoming Transactions.* vB\/s/);
});
cy.get('.row > :nth-child(2)').invoke('text').then((text) => {
expect(text).to.match(/Unconfirmed:(.*)/);

View File

@@ -72,13 +72,11 @@ Cypress.Commands.add('mockMempoolSocket', () => {
mockWebSocket();
});
Cypress.Commands.add('changeNetwork', (network: "testnet" | "signet" | "liquid" | "bisq" | "mainnet") => {
Cypress.Commands.add('changeNetwork', (network: "testnet" | "signet" | "liquid" | "mainnet") => {
cy.get('.dropdown-toggle').click().then(() => {
cy.get(`a.${network}`).click().then(() => {
cy.waitForPageIdle();
if (network !== 'bisq') {
cy.waitForSkeletonGone();
}
cy.waitForSkeletonGone();
});
});
});

View File

@@ -5,6 +5,6 @@ declare namespace Cypress {
waitForSkeletonGone(): Chainable<any>
waitForPageIdle(): Chainable<any>
mockMempoolSocket(): Chainable<any>
changeNetwork(network: "testnet"|"signet"|"liquid"|"bisq"|"mainnet"): Chainable<any>
changeNetwork(network: "testnet"|"signet"|"liquid"|"mainnet"): Chainable<any>
}
}

View File

@@ -4,11 +4,13 @@ const { spawnSync } = require('child_process');
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
const GENERATED_CONFIG_FILE_NAME = 'src/resources/config.js';
const GENERATED_TEMPLATE_CONFIG_FILE_NAME = 'src/resources/config.template.js';
const GENERATED_CUSTOMIZATION_FILE_NAME = 'src/resources/customize.js';
let settings = [];
let configContent = {};
let gitCommitHash = '';
let packetJsonVersion = '';
let customConfig;
try {
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
@@ -22,7 +24,13 @@ try {
}
}
const indexFilePath = configContent.BASE_MODULE ? 'src/index.' + configContent.BASE_MODULE + '.html' : 'src/index.mempool.html';
if (configContent && configContent.CUSTOMIZATION) {
customConfig = readConfig(configContent.CUSTOMIZATION);
}
const baseModuleName = configContent.BASE_MODULE || 'mempool';
const customBuildName = (customConfig && configContent.enterprise) ? ('.' + configContent.enterprise) : '';
const indexFilePath = 'src/index.' + baseModuleName + customBuildName + '.html';
try {
fs.copyFileSync(indexFilePath, 'src/index.html');
@@ -109,6 +117,17 @@ writeConfigTemplate(GENERATED_TEMPLATE_CONFIG_FILE_NAME, newConfigTemplate);
const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME);
let customConfigJs = '';
if (customConfig) {
console.log(`Customizing frontend using ${configContent.CUSTOMIZATION}`);
customConfigJs = `(function (window) {
window.__env = window.__env || {};
window.__env.customize = ${customConfig};
}((typeof global !== 'undefined') ? global : this));
`;
}
writeConfig(GENERATED_CUSTOMIZATION_FILE_NAME, customConfigJs);
if (currentConfig && currentConfig === newConfig) {
console.log(`No configuration updates, skipping ${GENERATED_CONFIG_FILE_NAME} file update`);
return;

View File

@@ -3,8 +3,6 @@
"SIGNET_ENABLED": false,
"LIQUID_ENABLED": false,
"LIQUID_TESTNET_ENABLED": false,
"BISQ_ENABLED": false,
"BISQ_SEPARATE_BACKEND": false,
"ITEMS_PER_PAGE": 10,
"KEEP_BLOCKS_AMOUNT": 8,
"NGINX_PROTOCOL": "http",
@@ -15,7 +13,6 @@
"BASE_MODULE": "mempool",
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
"LIQUID_WEBSITE_URL": "https://liquid.network",
"BISQ_WEBSITE_URL": "https://bisq.markets",
"MINING_DASHBOARD": true,
"AUDIT": false,
"MAINNET_BLOCK_AUDIT_START_HEIGHT": 0,
@@ -24,5 +21,6 @@
"LIGHTNING": false,
"HISTORICAL_PRICE": true,
"ADDITIONAL_CURRENCIES": false,
"ACCELERATOR": false
"ACCELERATOR": false,
"PUBLIC_ACCELERATIONS": false
}

View File

@@ -32,9 +32,10 @@
"bootstrap": "~4.6.2",
"browserify": "^17.0.0",
"clipboard": "^2.0.11",
"cypress": "^13.9.0",
"domino": "^2.1.6",
"echarts": "~5.5.0",
"esbuild": "^0.20.2",
"esbuild": "^0.21.1",
"lightweight-charts": "~3.8.0",
"ngx-echarts": "~17.1.0",
"ngx-infinite-scroll": "^17.0.0",
@@ -62,7 +63,7 @@
"optionalDependencies": {
"@cypress/schematic": "^2.5.0",
"@types/cypress": "^1.1.3",
"cypress": "^13.7.0",
"cypress": "^13.9.0",
"cypress-fail-on-console-error": "~5.1.0",
"cypress-wait-until": "^2.0.1",
"mock-socket": "~9.3.1",
@@ -3196,9 +3197,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.1.tgz",
"integrity": "sha512-O7yppwipkXvnEPjzkSXJRk2g4bS8sUx9p9oXHq9MU/U7lxUzZVsnFZMDTmeeX9bfQxrFcvOacl/ENgOh0WP9pA==",
"cpu": [
"ppc64"
],
@@ -3211,9 +3212,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.1.tgz",
"integrity": "sha512-hh3jKWikdnTtHCglDAeVO3Oyh8MaH8xZUaWMiCCvJ9/c3NtPqZq+CACOlGTxhddypXhl+8B45SeceYBfB/e8Ow==",
"cpu": [
"arm"
],
@@ -3226,9 +3227,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.1.tgz",
"integrity": "sha512-jXhccq6es+onw7x8MxoFnm820mz7sGa9J14kLADclmiEUH4fyj+FjR6t0M93RgtlI/awHWhtF0Wgfhqgf9gDZA==",
"cpu": [
"arm64"
],
@@ -3241,9 +3242,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.1.tgz",
"integrity": "sha512-NPObtlBh4jQHE01gJeucqEhdoD/4ya2owSIS8lZYS58aR0x7oZo9lB2lVFxgTANSa5MGCBeoQtr+yA9oKCGPvA==",
"cpu": [
"x64"
],
@@ -3256,9 +3257,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.1.tgz",
"integrity": "sha512-BLT7TDzqsVlQRmJfO/FirzKlzmDpBWwmCUlyggfzUwg1cAxVxeA4O6b1XkMInlxISdfPAOunV9zXjvh5x99Heg==",
"cpu": [
"arm64"
],
@@ -3271,9 +3272,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.1.tgz",
"integrity": "sha512-D3h3wBQmeS/vp93O4B+SWsXB8HvRDwMyhTNhBd8yMbh5wN/2pPWRW5o/hM3EKgk9bdKd9594lMGoTCTiglQGRQ==",
"cpu": [
"x64"
],
@@ -3286,9 +3287,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.1.tgz",
"integrity": "sha512-/uVdqqpNKXIxT6TyS/oSK4XE4xWOqp6fh4B5tgAwozkyWdylcX+W4YF2v6SKsL4wCQ5h1bnaSNjWPXG/2hp8AQ==",
"cpu": [
"arm64"
],
@@ -3301,9 +3302,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.1.tgz",
"integrity": "sha512-paAkKN1n1jJitw+dAoR27TdCzxRl1FOEITx3h201R6NoXUojpMzgMLdkXVgCvaCSCqwYkeGLoe9UVNRDKSvQgw==",
"cpu": [
"x64"
],
@@ -3316,9 +3317,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.1.tgz",
"integrity": "sha512-tRHnxWJnvNnDpNVnsyDhr1DIQZUfCXlHSCDohbXFqmg9W4kKR7g8LmA3kzcwbuxbRMKeit8ladnCabU5f2traA==",
"cpu": [
"arm"
],
@@ -3331,9 +3332,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.1.tgz",
"integrity": "sha512-G65d08YoH00TL7Xg4LaL3gLV21bpoAhQ+r31NUu013YB7KK0fyXIt05VbsJtpqh/6wWxoLJZOvQHYnodRrnbUQ==",
"cpu": [
"arm64"
],
@@ -3346,9 +3347,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.1.tgz",
"integrity": "sha512-tt/54LqNNAqCz++QhxoqB9+XqdsaZOtFD/srEhHYwBd3ZUOepmR1Eeot8bS+Q7BiEvy9vvKbtpHf+r6q8hF5UA==",
"cpu": [
"ia32"
],
@@ -3361,9 +3362,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.1.tgz",
"integrity": "sha512-MhNalK6r0nZD0q8VzUBPwheHzXPr9wronqmZrewLfP7ui9Fv1tdPmg6e7A8lmg0ziQCziSDHxh3cyRt4YMhGnQ==",
"cpu": [
"loong64"
],
@@ -3376,9 +3377,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.1.tgz",
"integrity": "sha512-YCKVY7Zen5rwZV+nZczOhFmHaeIxR4Zn3jcmNH53LbgF6IKRwmrMywqDrg4SiSNApEefkAbPSIzN39FC8VsxPg==",
"cpu": [
"mips64el"
],
@@ -3391,9 +3392,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.1.tgz",
"integrity": "sha512-bw7bcQ+270IOzDV4mcsKAnDtAFqKO0jVv3IgRSd8iM0ac3L8amvCrujRVt1ajBTJcpDaFhIX+lCNRKteoDSLig==",
"cpu": [
"ppc64"
],
@@ -3406,9 +3407,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.1.tgz",
"integrity": "sha512-ARmDRNkcOGOm1AqUBSwRVDfDeD9hGYRfkudP2QdoonBz1ucWVnfBPfy7H4JPI14eYtZruRSczJxyu7SRYDVOcg==",
"cpu": [
"riscv64"
],
@@ -3421,9 +3422,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.1.tgz",
"integrity": "sha512-o73TcUNMuoTZlhwFdsgr8SfQtmMV58sbgq6gQq9G1xUiYnHMTmJbwq65RzMx89l0iya69lR4bxBgtWiiOyDQZA==",
"cpu": [
"s390x"
],
@@ -3436,9 +3437,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.1.tgz",
"integrity": "sha512-da4/1mBJwwgJkbj4fMH7SOXq2zapgTo0LKXX1VUZ0Dxr+e8N0WbS80nSZ5+zf3lvpf8qxrkZdqkOqFfm57gXwA==",
"cpu": [
"x64"
],
@@ -3451,9 +3452,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.1.tgz",
"integrity": "sha512-CPWs0HTFe5woTJN5eKPvgraUoRHrCtzlYIAv9wBC+FAyagBSaf+UdZrjwYyTGnwPGkThV4OCI7XibZOnPvONVw==",
"cpu": [
"x64"
],
@@ -3466,9 +3467,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.1.tgz",
"integrity": "sha512-xxhTm5QtzNLc24R0hEkcH+zCx/o49AsdFZ0Cy5zSd/5tOj4X2g3/2AJB625NoadUuc4A8B3TenLJoYdWYOYCew==",
"cpu": [
"x64"
],
@@ -3481,9 +3482,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.1.tgz",
"integrity": "sha512-CWibXszpWys1pYmbr9UiKAkX6x+Sxw8HWtw1dRESK1dLW5fFJ6rMDVw0o8MbadusvVQx1a8xuOxnHXT941Hp1A==",
"cpu": [
"x64"
],
@@ -3496,9 +3497,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.1.tgz",
"integrity": "sha512-jb5B4k+xkytGbGUS4T+Z89cQJ9DJ4lozGRSV+hhfmCPpfJ3880O31Q1srPCimm+V6UCbnigqD10EgDNgjvjerQ==",
"cpu": [
"arm64"
],
@@ -3511,9 +3512,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.1.tgz",
"integrity": "sha512-PgyFvjJhXqHn1uxPhyN1wZ6dIomKjiLUQh1LjFvjiV1JmnkZ/oMPrfeEAZg5R/1ftz4LZWZr02kefNIQ5SKREQ==",
"cpu": [
"ia32"
],
@@ -3526,9 +3527,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.1.tgz",
"integrity": "sha512-W9NttRZQR5ehAiqHGDnvfDaGmQOm6Fi4vSlce8mjM75x//XKuVAByohlEX6N17yZnVXxQFuh4fDRunP8ca6bfA==",
"cpu": [
"x64"
],
@@ -8028,9 +8029,9 @@
"peer": true
},
"node_modules/cypress": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.0.tgz",
"integrity": "sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==",
"version": "13.9.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz",
"integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
@@ -9196,9 +9197,9 @@
}
},
"node_modules/esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.1.tgz",
"integrity": "sha512-GPqx+FX7mdqulCeQ4TsGZQ3djBJkx5k7zBGtqt9ycVlWNg8llJ4RO9n2vciu8BN2zAEs6lPbPl0asZsAh7oWzg==",
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -9207,29 +9208,29 @@
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
"@esbuild/aix-ppc64": "0.21.1",
"@esbuild/android-arm": "0.21.1",
"@esbuild/android-arm64": "0.21.1",
"@esbuild/android-x64": "0.21.1",
"@esbuild/darwin-arm64": "0.21.1",
"@esbuild/darwin-x64": "0.21.1",
"@esbuild/freebsd-arm64": "0.21.1",
"@esbuild/freebsd-x64": "0.21.1",
"@esbuild/linux-arm": "0.21.1",
"@esbuild/linux-arm64": "0.21.1",
"@esbuild/linux-ia32": "0.21.1",
"@esbuild/linux-loong64": "0.21.1",
"@esbuild/linux-mips64el": "0.21.1",
"@esbuild/linux-ppc64": "0.21.1",
"@esbuild/linux-riscv64": "0.21.1",
"@esbuild/linux-s390x": "0.21.1",
"@esbuild/linux-x64": "0.21.1",
"@esbuild/netbsd-x64": "0.21.1",
"@esbuild/openbsd-x64": "0.21.1",
"@esbuild/sunos-x64": "0.21.1",
"@esbuild/win32-arm64": "0.21.1",
"@esbuild/win32-ia32": "0.21.1",
"@esbuild/win32-x64": "0.21.1"
}
},
"node_modules/esbuild-wasm": {
@@ -9860,9 +9861,9 @@
"integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
},
"node_modules/express": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.1.tgz",
"integrity": "sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -20562,141 +20563,141 @@
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
},
"@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.1.tgz",
"integrity": "sha512-O7yppwipkXvnEPjzkSXJRk2g4bS8sUx9p9oXHq9MU/U7lxUzZVsnFZMDTmeeX9bfQxrFcvOacl/ENgOh0WP9pA==",
"optional": true
},
"@esbuild/android-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.1.tgz",
"integrity": "sha512-hh3jKWikdnTtHCglDAeVO3Oyh8MaH8xZUaWMiCCvJ9/c3NtPqZq+CACOlGTxhddypXhl+8B45SeceYBfB/e8Ow==",
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.1.tgz",
"integrity": "sha512-jXhccq6es+onw7x8MxoFnm820mz7sGa9J14kLADclmiEUH4fyj+FjR6t0M93RgtlI/awHWhtF0Wgfhqgf9gDZA==",
"optional": true
},
"@esbuild/android-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.1.tgz",
"integrity": "sha512-NPObtlBh4jQHE01gJeucqEhdoD/4ya2owSIS8lZYS58aR0x7oZo9lB2lVFxgTANSa5MGCBeoQtr+yA9oKCGPvA==",
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.1.tgz",
"integrity": "sha512-BLT7TDzqsVlQRmJfO/FirzKlzmDpBWwmCUlyggfzUwg1cAxVxeA4O6b1XkMInlxISdfPAOunV9zXjvh5x99Heg==",
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.1.tgz",
"integrity": "sha512-D3h3wBQmeS/vp93O4B+SWsXB8HvRDwMyhTNhBd8yMbh5wN/2pPWRW5o/hM3EKgk9bdKd9594lMGoTCTiglQGRQ==",
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.1.tgz",
"integrity": "sha512-/uVdqqpNKXIxT6TyS/oSK4XE4xWOqp6fh4B5tgAwozkyWdylcX+W4YF2v6SKsL4wCQ5h1bnaSNjWPXG/2hp8AQ==",
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.1.tgz",
"integrity": "sha512-paAkKN1n1jJitw+dAoR27TdCzxRl1FOEITx3h201R6NoXUojpMzgMLdkXVgCvaCSCqwYkeGLoe9UVNRDKSvQgw==",
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.1.tgz",
"integrity": "sha512-tRHnxWJnvNnDpNVnsyDhr1DIQZUfCXlHSCDohbXFqmg9W4kKR7g8LmA3kzcwbuxbRMKeit8ladnCabU5f2traA==",
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.1.tgz",
"integrity": "sha512-G65d08YoH00TL7Xg4LaL3gLV21bpoAhQ+r31NUu013YB7KK0fyXIt05VbsJtpqh/6wWxoLJZOvQHYnodRrnbUQ==",
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.1.tgz",
"integrity": "sha512-tt/54LqNNAqCz++QhxoqB9+XqdsaZOtFD/srEhHYwBd3ZUOepmR1Eeot8bS+Q7BiEvy9vvKbtpHf+r6q8hF5UA==",
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.1.tgz",
"integrity": "sha512-MhNalK6r0nZD0q8VzUBPwheHzXPr9wronqmZrewLfP7ui9Fv1tdPmg6e7A8lmg0ziQCziSDHxh3cyRt4YMhGnQ==",
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.1.tgz",
"integrity": "sha512-YCKVY7Zen5rwZV+nZczOhFmHaeIxR4Zn3jcmNH53LbgF6IKRwmrMywqDrg4SiSNApEefkAbPSIzN39FC8VsxPg==",
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.1.tgz",
"integrity": "sha512-bw7bcQ+270IOzDV4mcsKAnDtAFqKO0jVv3IgRSd8iM0ac3L8amvCrujRVt1ajBTJcpDaFhIX+lCNRKteoDSLig==",
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.1.tgz",
"integrity": "sha512-ARmDRNkcOGOm1AqUBSwRVDfDeD9hGYRfkudP2QdoonBz1ucWVnfBPfy7H4JPI14eYtZruRSczJxyu7SRYDVOcg==",
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.1.tgz",
"integrity": "sha512-o73TcUNMuoTZlhwFdsgr8SfQtmMV58sbgq6gQq9G1xUiYnHMTmJbwq65RzMx89l0iya69lR4bxBgtWiiOyDQZA==",
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.1.tgz",
"integrity": "sha512-da4/1mBJwwgJkbj4fMH7SOXq2zapgTo0LKXX1VUZ0Dxr+e8N0WbS80nSZ5+zf3lvpf8qxrkZdqkOqFfm57gXwA==",
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.1.tgz",
"integrity": "sha512-CPWs0HTFe5woTJN5eKPvgraUoRHrCtzlYIAv9wBC+FAyagBSaf+UdZrjwYyTGnwPGkThV4OCI7XibZOnPvONVw==",
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.1.tgz",
"integrity": "sha512-xxhTm5QtzNLc24R0hEkcH+zCx/o49AsdFZ0Cy5zSd/5tOj4X2g3/2AJB625NoadUuc4A8B3TenLJoYdWYOYCew==",
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.1.tgz",
"integrity": "sha512-CWibXszpWys1pYmbr9UiKAkX6x+Sxw8HWtw1dRESK1dLW5fFJ6rMDVw0o8MbadusvVQx1a8xuOxnHXT941Hp1A==",
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.1.tgz",
"integrity": "sha512-jb5B4k+xkytGbGUS4T+Z89cQJ9DJ4lozGRSV+hhfmCPpfJ3880O31Q1srPCimm+V6UCbnigqD10EgDNgjvjerQ==",
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.1.tgz",
"integrity": "sha512-PgyFvjJhXqHn1uxPhyN1wZ6dIomKjiLUQh1LjFvjiV1JmnkZ/oMPrfeEAZg5R/1ftz4LZWZr02kefNIQ5SKREQ==",
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.1.tgz",
"integrity": "sha512-W9NttRZQR5ehAiqHGDnvfDaGmQOm6Fi4vSlce8mjM75x//XKuVAByohlEX6N17yZnVXxQFuh4fDRunP8ca6bfA==",
"optional": true
},
"@eslint-community/eslint-utils": {
@@ -24111,9 +24112,9 @@
"peer": true
},
"cypress": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.0.tgz",
"integrity": "sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==",
"version": "13.9.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz",
"integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==",
"optional": true,
"requires": {
"@cypress/request": "^3.0.0",
@@ -25031,33 +25032,33 @@
}
},
"esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.1.tgz",
"integrity": "sha512-GPqx+FX7mdqulCeQ4TsGZQ3djBJkx5k7zBGtqt9ycVlWNg8llJ4RO9n2vciu8BN2zAEs6lPbPl0asZsAh7oWzg==",
"requires": {
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
"@esbuild/aix-ppc64": "0.21.1",
"@esbuild/android-arm": "0.21.1",
"@esbuild/android-arm64": "0.21.1",
"@esbuild/android-x64": "0.21.1",
"@esbuild/darwin-arm64": "0.21.1",
"@esbuild/darwin-x64": "0.21.1",
"@esbuild/freebsd-arm64": "0.21.1",
"@esbuild/freebsd-x64": "0.21.1",
"@esbuild/linux-arm": "0.21.1",
"@esbuild/linux-arm64": "0.21.1",
"@esbuild/linux-ia32": "0.21.1",
"@esbuild/linux-loong64": "0.21.1",
"@esbuild/linux-mips64el": "0.21.1",
"@esbuild/linux-ppc64": "0.21.1",
"@esbuild/linux-riscv64": "0.21.1",
"@esbuild/linux-s390x": "0.21.1",
"@esbuild/linux-x64": "0.21.1",
"@esbuild/netbsd-x64": "0.21.1",
"@esbuild/openbsd-x64": "0.21.1",
"@esbuild/sunos-x64": "0.21.1",
"@esbuild/win32-arm64": "0.21.1",
"@esbuild/win32-ia32": "0.21.1",
"@esbuild/win32-x64": "0.21.1"
}
},
"esbuild-wasm": {
@@ -25526,9 +25527,9 @@
"integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
},
"express": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.1.tgz",
"integrity": "sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",

View File

@@ -34,13 +34,12 @@
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod",
"start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging",
"start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed",
"build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets && npm run build-mempool.js",
"build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets-dev && npm run sync-assets && npm run build-mempool.js",
"sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'",
"sync-assets-dev": "node sync-assets.js 'src/resources/'",
"generate-config": "node generate-config.js",
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js",
"build-mempool-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
"build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
"build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
"test": "npm run ng -- test",
"lint": "./node_modules/.bin/eslint . --ext .ts",
@@ -51,17 +50,16 @@
"dev:ssr": "npm run generate-config && ng run mempool:serve-ssr",
"serve:ssr": "npm run generate-config && node server.run.js",
"build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts",
"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",
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_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 ITEMS_PER_PAGE=25 BASE_MODULE=liquid BLOCK_WEIGHT_UNITS=300000 && npm run generate-config",
"prerender": "npm run ng -- run mempool:prerender",
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"cypress:run:record": "cypress run --record",
"cypress:open:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:open",
"cypress:run:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record",
"cypress:open:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:open",
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record"
"cypress:open:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:open",
"cypress:run:ci": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record",
"cypress:open:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:open",
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record"
},
"dependencies": {
"@angular-devkit/build-angular": "^17.3.1",
@@ -94,7 +92,7 @@
"ngx-infinite-scroll": "^17.0.0",
"qrcode": "1.5.1",
"rxjs": "~7.8.1",
"esbuild": "^0.20.2",
"esbuild": "^0.21.1",
"tinyify": "^4.0.0",
"tlite": "^0.1.9",
"tslib": "~2.6.0",
@@ -117,7 +115,7 @@
"optionalDependencies": {
"@cypress/schematic": "^2.5.0",
"@types/cypress": "^1.1.3",
"cypress": "^13.7.0",
"cypress": "^13.9.0",
"cypress-fail-on-console-error": "~5.1.0",
"cypress-wait-until": "^2.0.1",
"mock-socket": "~9.3.1",

View File

@@ -22,7 +22,6 @@ PROXY_CONFIG = [
{
context: ['*',
'/api/**', '!/api/v1/ws',
'!/bisq', '!/bisq/**', '!/bisq/',
'!/liquid', '!/liquid/**', '!/liquid/',
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
'/testnet/api/**', '/signet/api/**'
@@ -39,16 +38,6 @@ PROXY_CONFIG = [
secure: false,
changeOrigin: true,
},
{
context: ['/api/bisq**', '/bisq/api/**'],
target: "https://bisq.markets",
pathRewrite: {
"^/api/bisq/": "/bisq/api"
},
ws: true,
secure: false,
changeOrigin: true
},
{
context: ['/api/liquid**', '/liquid/api/**'],
target: "https://liquid.network",

View File

@@ -67,40 +67,6 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
]);
}
if (configContent && configContent.BASE_MODULE === 'bisq') {
PROXY_CONFIG.push(...[
{
context: ['/bisq/api/v1/ws'],
target: `http://127.0.0.1:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq": ""
},
},
{
context: ['/bisq/api/v1/**'],
target: `http://127.0.0.1:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
},
{
context: ['/bisq/api/**'],
target: `http://127.0.0.1:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq/api/": "/api/v1/bisq/"
},
}
]);
}
PROXY_CONFIG.push(...[
{
context: ['/testnet/api/v1/lightning/**'],

View File

@@ -67,40 +67,6 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
]);
}
if (configContent && configContent.BASE_MODULE === 'bisq') {
PROXY_CONFIG.push(...[
{
context: ['/bisq/api/v1/ws'],
target: `http://localhost:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq": ""
},
},
{
context: ['/bisq/api/v1/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
},
{
context: ['/bisq/api/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq/api/": "/api/v1/bisq/"
},
}
]);
}
PROXY_CONFIG.push(...[
{
context: ['/testnet/api/v1/lightning/**'],

View File

@@ -61,39 +61,6 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
]);
}
if (configContent && configContent.BASE_MODULE === 'bisq') {
PROXY_CONFIG.push(...[
{
context: ['/bisq/api/v1/ws'],
target: `http://localhost:8999`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq": ""
},
},
{
context: ['/bisq/api/v1/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
},
{
context: ['/bisq/api/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/bisq/api/": "/api/v1/bisq/"
},
},
]);
}
PROXY_CONFIG.push(...[
{
context: ['/api/v1/services/**'],

View File

@@ -5,7 +5,6 @@ let PROXY_CONFIG = require('./proxy.conf');
PROXY_CONFIG.forEach(entry => {
entry.target = entry.target.replace("mempool.space", "mempool-staging.fra.mempool.space");
entry.target = entry.target.replace("liquid.network", "liquid-staging.fra.mempool.space");
entry.target = entry.target.replace("bisq.markets", "bisq-staging.fra.mempool.space");
});
module.exports = PROXY_CONFIG;

View File

@@ -7,6 +7,8 @@ import { MempoolBlockViewComponent } from './components/mempool-block-view/mempo
import { ClockComponent } from './components/clock/clock.component';
import { StatusViewComponent } from './components/status-view/status-view.component';
import { AddressGroupComponent } from './components/address-group/address-group.component';
import { TrackerComponent } from './components/tracker/tracker.component';
import { AccelerateCheckout } from './components/accelerate-checkout/accelerate-checkout.component';
const browserWindow = window || {};
// @ts-ignore
@@ -51,6 +53,44 @@ let routes: Routes = [
},
]
},
{
path: 'testnet4',
children: [
{
path: '',
pathMatch: 'full',
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
data: { preload: true },
},
{
path: '',
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
data: { preload: true },
},
{
path: 'wallet',
children: [],
component: AddressGroupComponent,
data: {
networkSpecific: true,
}
},
{
path: 'status',
data: { networks: ['bitcoin', 'liquid'] },
component: StatusViewComponent
},
{
path: '',
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
data: { preload: true },
},
{
path: '**',
redirectTo: '/testnet4'
},
]
},
{
path: 'signet',
children: [
@@ -105,6 +145,10 @@ let routes: Routes = [
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
data: { preload: true },
},
{
path: 'tracker/:id',
component: TrackerComponent,
},
{
path: 'wallet',
children: [],
@@ -124,6 +168,10 @@ let routes: Routes = [
path: 'testnet',
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
},
{
path: 'testnet4',
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
},
{
path: 'signet',
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
@@ -170,13 +218,6 @@ let routes: Routes = [
},
];
if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'bisq') {
routes = [{
path: '',
loadChildren: () => import('./bisq/bisq.module').then(m => m.BisqModule)
}];
}
if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
routes = [
{
@@ -270,7 +311,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
imports: [RouterModule.forRoot(routes, {
initialNavigation: 'enabledBlocking',
scrollPositionRestoration: 'enabled',
anchorScrolling: 'enabled',
anchorScrolling: 'disabled',
preloadingStrategy: AppPreloadingStrategy
})],
})

View File

@@ -1,4 +1,4 @@
export const mempoolFeeColors = [
export const defaultMempoolFeeColors = [
'557d00',
'5d7d01',
'637d02',
@@ -39,6 +39,47 @@ export const mempoolFeeColors = [
'ae005b',
];
export const contrastMempoolFeeColors = [
'0082e6',
'0984df',
'1285d9',
'1a87d2',
'2388cb',
'2c8ac5',
'358bbe',
'3e8db7',
'468eb0',
'4f90aa',
'5892a3',
'61939c',
'6a9596',
'72968f',
'7b9888',
'849982',
'8d9b7b',
'959c74',
'9e9e6e',
'a79f67',
'b0a160',
'b9a35a',
'c1a453',
'caa64c',
'd3a745',
'dca93f',
'e5aa38',
'edac31',
'f6ad2b',
'ffaf24',
'ffb01e',
'ffb118',
'ffb212',
'ffb30c',
'ffb406',
'ffb500',
'ffb600',
'ffb700',
];
export const chartColors = [
"#D81B60",
"#8E24AA",
@@ -148,22 +189,22 @@ export const specialBlocks = {
'0': {
labelEvent: 'Genesis',
labelEventCompleted: 'The Genesis of Bitcoin',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'210000': {
labelEvent: 'Bitcoin\'s 1st Halving',
labelEventCompleted: 'Block Subsidy has halved to 25 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'420000': {
labelEvent: 'Bitcoin\'s 2nd Halving',
labelEventCompleted: 'Block Subsidy has halved to 12.5 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'630000': {
labelEvent: 'Bitcoin\'s 3rd Halving',
labelEventCompleted: 'Block Subsidy has halved to 6.25 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'709632': {
labelEvent: 'Taproot 🌱 activation',
@@ -173,62 +214,62 @@ export const specialBlocks = {
'840000': {
labelEvent: 'Bitcoin\'s 4th Halving',
labelEventCompleted: 'Block Subsidy has halved to 3.125 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'1050000': {
labelEvent: 'Bitcoin\'s 5th Halving',
labelEventCompleted: 'Block Subsidy has halved to 1.5625 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'1260000': {
labelEvent: 'Bitcoin\'s 6th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.78125 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'1470000': {
labelEvent: 'Bitcoin\'s 7th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.390625 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'1680000': {
labelEvent: 'Bitcoin\'s 8th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.1953125 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'1890000': {
labelEvent: 'Bitcoin\'s 9th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.09765625 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'2100000': {
labelEvent: 'Bitcoin\'s 10th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.04882812 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'2310000': {
labelEvent: 'Bitcoin\'s 11th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.02441406 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'2520000': {
labelEvent: 'Bitcoin\'s 12th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.01220703 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'2730000': {
labelEvent: 'Bitcoin\'s 13th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.00610351 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'2940000': {
labelEvent: 'Bitcoin\'s 14th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.00305175 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
},
'3150000': {
labelEvent: 'Bitcoin\'s 15th Halving',
labelEventCompleted: 'Block Subsidy has halved to 0.00152587 BTC per block',
networks: ['mainnet', 'testnet'],
networks: ['mainnet', 'testnet', 'testnet4'],
}
};

View File

@@ -19,6 +19,7 @@ import { SharedModule } from './shared/shared.module';
import { StorageService } from './services/storage.service';
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
import { LanguageService } from './services/language.service';
import { ThemeService } from './services/theme.service';
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
import { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe';
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
@@ -38,6 +39,7 @@ const providers = [
StorageService,
EnterpriseService,
LanguageService,
ThemeService,
ShortenStringPipe,
FiatShortenerPipe,
FiatCurrencyPipe,

View File

@@ -1,108 +0,0 @@
<div class="container-xl">
<h1 i18n="shared.address">Address</h1>
<span class="address-link">
<app-truncate [text]="addressString" [lastChars]="8" [link]="['/address/' | relativeUrl, addressString]">
<app-clipboard [text]="addressString"></app-clipboard>
</app-truncate>
</span>
<br>
<div class="clearfix"></div>
<ng-template [ngIf]="!isLoadingAddress && !error">
<div class="box">
<div class="row">
<div class="col-md">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td i18n="address.total-received">Total received</td>
<td>{{ totalReceived / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
</tr>
<tr>
<td i18n="address.total-sent">Total sent</td>
<td>{{ totalSent / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
</tr>
<tr>
<td i18n="address.balance">Balance</td>
<td>{{ (totalReceived - totalSent) / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span> <span class="fiat"><app-bsq-amount [bsq]="totalReceived - totalSent" [forceFiat]="true" [green]="true"></app-bsq-amount></span></td>
</tr>
</tbody>
</table>
</div>
<div class="w-100 d-block d-md-none"></div>
<div class="col-md qrcode-col">
<div class="qr-wrapper">
<app-qrcode [data]="addressString"></app-qrcode>
</div>
</div>
</div>
</div>
<br>
<h2>
<ng-container *ngTemplateOutlet="transactions.length === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: transactions.length}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
</h2>
<ng-template ngFor let-tx [ngForOf]="transactions">
<div class="header-bg box" style="padding: 10px; margin-bottom: 10px;">
<a [routerLink]="['/tx/' | relativeUrl, tx.id]" [state]="{ data: tx }">
<span style="float: left;" class="d-block d-md-none">{{ tx.id | shortenString : 16 }}</span>
<span style="float: left;" class="d-none d-md-block">{{ tx.id }}</span>
</a>
<div class="float-right">
&lrm;{{ tx.time | date:'yyyy-MM-dd HH:mm' }}
</div>
<div class="clearfix"></div>
</div>
<app-bisq-transfers [tx]="tx" [showConfirmations]="true"></app-bisq-transfers>
<br>
</ng-template>
</ng-template>
<ng-template [ngIf]="isLoadingAddress && !error">
<div class="box">
<div class="row">
<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>
<div class="w-100 d-block d-md-none"></div>
<div class="col">
</div>
</div>
</div>
</ng-template>
<ng-template [ngIf]="error">
<app-http-error [error]="error">
<span i18n="address.error.loading-address-data">Error loading address data.</span>
</app-http-error>
</ng-template>
</div>
<br>

View File

@@ -1,75 +0,0 @@
.qr-wrapper {
background-color: #FFF;
padding: 10px;
padding-bottom: 5px;
display: inline-block;
}
.qrcode-col {
text-align: center;
}
.qrcode-col > div {
margin: 20px auto 5px;
@media (min-width: 768px) {
text-align: center;
margin: auto;
}
}
.fiat {
display: block;
font-size: 13px;
@media (min-width: 768px) {
display: inline-block;
font-size: 14px;
margin-left: 10px;
}
}
.table {
tr td {
&:last-child {
text-align: right;
@media (min-width: 768px) {
text-align: left;
}
}
}
}
h1 {
margin: 0px;
padding: 0px;
@media (min-width: 576px) {
float: left;
margin-right: 10px;
}
}
.address-link {
line-height: 26px;
margin-left: 0px;
top: 14px;
position: relative;
display: flex;
flex-direction: row;
@media (min-width: 768px) {
line-height: 38px;
}
}
.row{
flex-direction: column;
@media (min-width: 576px) {
flex-direction: row;
}
}
@media (max-width: 767.98px) {
.mobile-bottomcol {
margin-top: 15px;
}
.details-table td:first-child {
white-space: pre-wrap;
}
}

View File

@@ -1,89 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SeoService } from '../../services/seo.service';
import { switchMap, filter, catchError } from 'rxjs/operators';
import { ParamMap, ActivatedRoute } from '@angular/router';
import { Subscription, of } from 'rxjs';
import { BisqTransaction } from '../bisq.interfaces';
import { BisqApiService } from '../bisq-api.service';
import { WebsocketService } from '../../services/websocket.service';
@Component({
selector: 'app-bisq-address',
templateUrl: './bisq-address.component.html',
styleUrls: ['./bisq-address.component.scss']
})
export class BisqAddressComponent implements OnInit, OnDestroy {
transactions: BisqTransaction[];
addressString: string;
isLoadingAddress = true;
error: any;
mainSubscription: Subscription;
totalReceived = 0;
totalSent = 0;
constructor(
private websocketService: WebsocketService,
private route: ActivatedRoute,
private seoService: SeoService,
private bisqApiService: BisqApiService,
) { }
ngOnInit() {
this.websocketService.want(['blocks']);
this.mainSubscription = this.route.paramMap
.pipe(
switchMap((params: ParamMap) => {
this.error = undefined;
this.isLoadingAddress = true;
this.transactions = null;
document.body.scrollTo(0, 0);
this.addressString = params.get('id') || '';
this.seoService.setTitle($localize`:@@bisq-address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
this.seoService.setDescription($localize`:@@meta.description.bisq.address:See current balance, pending transactions, and history of confirmed transactions for BSQ address ${this.addressString}:INTERPOLATION:.`);
return this.bisqApiService.getAddress$(this.addressString)
.pipe(
catchError((err) => {
this.isLoadingAddress = false;
this.error = err;
this.seoService.logSoft404();
console.log(err);
return of(null);
})
);
}),
filter((transactions) => transactions !== null)
)
.subscribe((transactions: BisqTransaction[]) => {
this.transactions = transactions;
this.updateChainStats();
this.isLoadingAddress = false;
},
(error) => {
console.log(error);
this.error = error;
this.seoService.logSoft404();
this.isLoadingAddress = false;
});
}
updateChainStats() {
const shortenedAddress = this.addressString.substr(1);
this.totalSent = this.transactions.reduce((acc, tx) =>
acc + tx.inputs
.filter((input) => input.address === shortenedAddress)
.reduce((a, input) => a + input.bsqAmount, 0), 0);
this.totalReceived = this.transactions.reduce((acc, tx) =>
acc + tx.outputs
.filter((output) => output.address === shortenedAddress)
.reduce((a, output) => a + output.bsqAmount, 0), 0);
}
ngOnDestroy() {
this.mainSubscription.unsubscribe();
}
}

View File

@@ -1,78 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { BisqTransaction, BisqBlock, BisqStats, MarketVolume, Trade, Markets, Tickers, Offers, Currencies, HighLowOpenClose, SummarizedInterval } from './bisq.interfaces';
const API_BASE_URL = '/bisq/api';
@Injectable({
providedIn: 'root'
})
export class BisqApiService {
apiBaseUrl: string;
constructor(
private httpClient: HttpClient,
) { }
getStats$(): Observable<BisqStats> {
return this.httpClient.get<BisqStats>(API_BASE_URL + '/stats');
}
getTransaction$(txId: string): Observable<BisqTransaction> {
return this.httpClient.get<BisqTransaction>(API_BASE_URL + '/tx/' + txId);
}
listTransactions$(start: number, length: number, types: string[]): Observable<HttpResponse<BisqTransaction[]>> {
let params = new HttpParams();
types.forEach((t: string) => {
params = params.append('types[]', t);
});
return this.httpClient.get<BisqTransaction[]>(API_BASE_URL + `/txs/${start}/${length}`, { params, observe: 'response' });
}
getBlock$(hash: string): Observable<BisqBlock> {
return this.httpClient.get<BisqBlock>(API_BASE_URL + '/block/' + hash);
}
listBlocks$(start: number, length: number): Observable<HttpResponse<BisqBlock[]>> {
return this.httpClient.get<BisqBlock[]>(API_BASE_URL + `/blocks/${start}/${length}`, { observe: 'response' });
}
getAddress$(address: string): Observable<BisqTransaction[]> {
return this.httpClient.get<BisqTransaction[]>(API_BASE_URL + '/address/' + address);
}
getMarkets$(): Observable<Markets> {
return this.httpClient.get<Markets>(API_BASE_URL + '/markets/markets');
}
getMarketsTicker$(): Observable<Tickers> {
return this.httpClient.get<Tickers>(API_BASE_URL + '/markets/ticker');
}
getMarketsCurrencies$(): Observable<Currencies> {
return this.httpClient.get<Currencies>(API_BASE_URL + '/markets/currencies');
}
getMarketsHloc$(market: string, interval: 'minute' | 'half_hour' | 'hour' | 'half_day' | 'day'
| 'week' | 'month' | 'year' | 'auto'): Observable<SummarizedInterval[]> {
return this.httpClient.get<SummarizedInterval[]>(API_BASE_URL + '/markets/hloc?market=' + market + '&interval=' + interval);
}
getMarketOffers$(market: string): Observable<Offers> {
return this.httpClient.get<Offers>(API_BASE_URL + '/markets/offers?market=' + market);
}
getMarketTrades$(market: string): Observable<Trade[]> {
return this.httpClient.get<Trade[]>(API_BASE_URL + '/markets/trades?market=' + market);
}
getMarketVolumesByTime$(period: string): Observable<HighLowOpenClose[]> {
return this.httpClient.get<HighLowOpenClose[]>(API_BASE_URL + '/markets/volumes/' + period);
}
getAllVolumesDay$(): Observable<MarketVolume[]> {
return this.httpClient.get<MarketVolume[]>(API_BASE_URL + '/markets/volumes?interval=week');
}
}

View File

@@ -1,114 +0,0 @@
<div class="container-xl">
<div class="title-block">
<h1><ng-template [ngIf]="blockHeight" i18n="shared.block-title">Block <ng-container *ngTemplateOutlet="blockTemplateContent"></ng-container></ng-template></h1>
</div>
<ng-template #blockTemplateContent><a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template>
<div class="clearfix"></div>
<ng-template [ngIf]="!isLoading && !error">
<div class="box block-container">
<div class="row">
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width" i18n="block.hash">Hash</td>
<td><a [routerLink]="['/block/' | relativeUrl, block.hash]" title="{{ block.hash }}">{{ block.hash | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.hash"></app-clipboard></td>
</tr>
<tr>
<td i18n="block.timestamp">Timestamp</td>
<td>
&lrm;{{ block.time | date:'yyyy-MM-dd HH:mm' }}
<div class="lg-inline">
<i class="symbol">(<app-time kind="since" [time]="block.time / 1000" [fastRender]="true"></app-time>)</i>
</div>
</td>
</tr>
</table>
</div>
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width" i18n="block.previous_hash|Transaction Previous Hash">Previous hash</td>
<td><a [routerLink]="['/block/' | relativeUrl, block.previousBlockHash]" title="{{ block.hash }}">{{ block.previousBlockHash | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.previousBlockHash"></app-clipboard></td>
</tr>
</table>
</div>
</div>
</div>
<div class="clearfix"></div>
<br>
<h2>
<ng-container *ngTemplateOutlet="block.txs.length === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.txs.length| number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
</h2>
<ng-template ngFor let-tx [ngForOf]="block.txs">
<div class="header-bg box" style="padding: 10px; margin-bottom: 10px;">
<a [routerLink]="['/tx/' | relativeUrl, tx.id]" [state]="{ data: tx }">
<span style="float: left;" class="d-block d-md-none">{{ tx.id | shortenString : 16 }}</span>
<span style="float: left;" class="d-none d-md-block">{{ tx.id }}</span>
</a>
<div class="float-right">
&lrm;{{ tx.time | date:'yyyy-MM-dd HH:mm' }}
</div>
<div class="clearfix"></div>
</div>
<app-bisq-transfers [tx]="tx" [showConfirmations]="true"></app-bisq-transfers>
<br>
</ng-template>
</ng-template>
<ng-template [ngIf]="isLoading && !error">
<div class="box">
<div class="row">
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width" i18n="block.hash">Hash</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td i18n="block.timestamp">Timestamp</td>
<td><span class="skeleton-loader"></span></td>
</tr>
</table>
</div>
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width" i18n="block.previous_hash|Transaction Previous Hash">Previous hash</td>
<td><span class="skeleton-loader"></span></td>
</tr>
</table>
</div>
</div>
</div>
</ng-template>
<ng-template [ngIf]="error">
<div class="clearfix"></div>
<div class="text-center">
Error loading block
<br>
<i>{{ error.status }}: {{ error.statusText }}</i>
</div>
</ng-template>
</div>

View File

@@ -1,44 +0,0 @@
.td-width {
width: 140px;
@media (min-width: 768px) {
width: 175px;
}
}
h1 {
margin: 0px;
padding: 0px;
@media (min-width: 576px) {
float: left;
margin-right: 10px;
}
}
.row{
flex-direction: column;
@media (min-width: 768px) {
flex-direction: row;
}
}
.block-container {
.table {
tr td {
&:last-child {
text-align: right;
@media (min-width: 992px) {
text-align: left;
}
}
}
}
.fiat {
display: block;
font-size: 13px;
@media (min-width: 992px) {
display: inline-block;
font-size: 14px;
margin-left: 10px;
}
}
}

View File

@@ -1,106 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { BisqBlock } from '../../bisq/bisq.interfaces';
import { Location } from '@angular/common';
import { BisqApiService } from '../bisq-api.service';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Subscription, of } from 'rxjs';
import { switchMap, catchError } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { HttpErrorResponse } from '@angular/common/http';
import { WebsocketService } from '../../services/websocket.service';
@Component({
selector: 'app-bisq-block',
templateUrl: './bisq-block.component.html',
styleUrls: ['./bisq-block.component.scss']
})
export class BisqBlockComponent implements OnInit, OnDestroy {
block: BisqBlock;
subscription: Subscription;
blockHash = '';
blockHeight = 0;
isLoading = true;
error: HttpErrorResponse | null;
constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService,
private route: ActivatedRoute,
private seoService: SeoService,
private electrsApiService: ElectrsApiService,
private router: Router,
private location: Location,
) { }
ngOnInit(): void {
this.websocketService.want(['blocks']);
this.subscription = this.route.paramMap
.pipe(
switchMap((params: ParamMap) => {
const blockHash = params.get('id') || '';
document.body.scrollTo(0, 0);
this.isLoading = true;
this.error = null;
if (history.state.data && history.state.data.blockHeight) {
this.blockHeight = history.state.data.blockHeight;
}
if (history.state.data && history.state.data.block) {
this.blockHeight = history.state.data.block.height;
return of(history.state.data.block);
}
let isBlockHeight = false;
if (/^[0-9]+$/.test(blockHash)) {
isBlockHeight = true;
} else {
this.blockHash = blockHash;
}
if (isBlockHeight) {
return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockHash, 10))
.pipe(
switchMap((hash) => {
if (!hash) {
return;
}
this.blockHash = hash;
this.location.replaceState(
this.router.createUrlTree(['/bisq/block/', hash]).toString()
);
this.seoService.updateCanonical(this.location.path());
return this.bisqApiService.getBlock$(this.blockHash)
.pipe(catchError(this.caughtHttpError.bind(this)));
}),
catchError(this.caughtHttpError.bind(this))
);
}
return this.bisqApiService.getBlock$(this.blockHash)
.pipe(catchError(this.caughtHttpError.bind(this)));
})
)
.subscribe((block: BisqBlock) => {
if (!block) {
this.seoService.logSoft404();
return;
}
this.isLoading = false;
this.blockHeight = block.height;
this.seoService.setTitle($localize`:@@bisq-block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.hash}:BLOCK_HASH:`);
this.seoService.setDescription($localize`:@@meta.description.bisq.block:See all BSQ transactions in Bitcoin block ${block.height}:BLOCK_HEIGHT: (block hash ${block.hash}:BLOCK_HASH:).`);
this.block = block;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
caughtHttpError(err: HttpErrorResponse){
this.error = err;
this.seoService.logSoft404();
return of(null);
}
}

View File

@@ -1,40 +0,0 @@
<div class="container-xl" (window:resize)="onResize($event)">
<h1 style="float: left;" i18n="Bisq blocks header">BSQ Blocks</h1>
<br>
<div class="clearfix"></div>
<ng-container *ngIf="{ value: (blocks$ | async) } as blocks">
<div class="table-responsive-sm">
<table class="table table-borderless table-striped">
<thead>
<th style="width: 25%;" i18n="Bisq block height header">Height</th>
<th style="width: 25%;" i18n="Bisq block confirmed time header">Confirmed</th>
<th style="width: 25%;" i18n="Bisq block total BSQ tokens sent header">Total sent</th>
<th class="d-none d-md-block" style="width: 25%;" i18n="Bisq block transactions title">Transactions</th>
</thead>
<tbody *ngIf="blocks.value; else loadingTmpl">
<tr *ngFor="let block of blocks.value[0]; trackBy: trackByFn">
<td><a [routerLink]="['/block/' | relativeUrl, block.hash]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
<td><app-time kind="since" [time]="block.time / 1000" [fastRender]="true"></app-time></td>
<td>{{ calculateTotalOutput(block) / 100 | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
<td class="d-none d-md-block">{{ block.txs.length }}</td>
</tr>
</tbody>
</table>
</div>
<br>
<ngb-pagination *ngIf="blocks.value" class="pagination-container" [size]="paginationSize" [collectionSize]="blocks.value[1]" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
<div class="clearfix"></div>
<br>
</ng-container>
</div>
<ng-template #loadingTmpl>
<tr *ngFor="let i of loadingItems">
<td *ngFor="let j of [1, 2, 3, 4]"><span class="skeleton-loader"></span></td>
</tr>
</ng-template>

View File

@@ -1,6 +0,0 @@
.pagination-container {
float: none;
@media(min-width: 400px){
float: right;
}
}

View File

@@ -1,91 +0,0 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { BisqApiService } from '../bisq-api.service';
import { switchMap, map, take, mergeMap, tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { BisqBlock, BisqOutput, BisqTransaction } from '../bisq.interfaces';
import { SeoService } from '../../services/seo.service';
import { ActivatedRoute, Router } from '@angular/router';
import { WebsocketService } from '../../services/websocket.service';
@Component({
selector: 'app-bisq-blocks',
templateUrl: './bisq-blocks.component.html',
styleUrls: ['./bisq-blocks.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BisqBlocksComponent implements OnInit {
blocks$: Observable<[BisqBlock[], number]>;
page = 1;
itemsPerPage: number;
contentSpace = window.innerHeight - (165 + 75);
fiveItemsPxSize = 250;
loadingItems: number[];
isLoading = true;
// @ts-ignore
paginationSize: 'sm' | 'lg' = 'md';
paginationMaxSize = 5;
constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService,
private seoService: SeoService,
private route: ActivatedRoute,
private router: Router,
) { }
ngOnInit(): void {
this.websocketService.want(['blocks']);
this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`);
this.seoService.setDescription($localize`:@@meta.description.bisq.blocks:See a list of recent Bitcoin blocks with BSQ transactions, total BSQ sent per block, and more.`);
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
this.loadingItems = Array(this.itemsPerPage);
if (document.body.clientWidth < 670) {
this.paginationSize = 'sm';
this.paginationMaxSize = 3;
}
this.blocks$ = this.route.queryParams
.pipe(
take(1),
tap((qp) => {
if (qp.page) {
this.page = parseInt(qp.page, 10);
}
}),
mergeMap(() => this.route.queryParams),
map((queryParams) => {
if (queryParams.page) {
const newPage = parseInt(queryParams.page, 10);
this.page = newPage;
return newPage;
} else {
this.page = 1;
}
return 1;
}),
switchMap((page) => this.bisqApiService.listBlocks$((page - 1) * this.itemsPerPage, this.itemsPerPage)),
map((response) => [response.body, parseInt(response.headers.get('x-total-count'), 10)]),
);
}
calculateTotalOutput(block: BisqBlock): number {
return block.txs.reduce((a: number, tx: BisqTransaction) =>
a + tx.outputs.reduce((acc: number, output: BisqOutput) => acc + output.bsqAmount, 0), 0
);
}
trackByFn(index: number) {
return index;
}
pageChange(page: number) {
this.router.navigate([], {
queryParams: { page: page },
queryParamsHandling: 'merge',
});
}
onResize(event: any) {
this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
}
}

View File

@@ -1,63 +0,0 @@
<div class="container-xl">
<h1 i18n="Bisq markets title">Bisq Trading Volume</h1>
<div id="volumeHolder">
<ng-template #loadingVolumes>
<div class="text-center loadingVolumes">
<div class="spinner-border text-light"></div>
</div>
</ng-template>
<ng-container *ngIf="volumes$ | async as volumes; else loadingVolumes">
<app-lightweight-charts-area [data]="volumes.data" [lineData]="volumes.linesData"></app-lightweight-charts-area>
</ng-container>
</div>
<br><br>
<div class="container-info">
<h1>
<ng-template [ngIf]="stateService.env.BASE_MODULE === 'bisq'" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
</h1>
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
<div class="table-container">
<table class="table table-borderless table-striped">
<thead>
<th><ng-container i18n>Currency</ng-container> <button [disabled]="(sort$ | async) === 'name'" class="btn btn-link btn-sm" (click)="sort('name')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
<th i18n>Price</th>
<th><ng-container i18n="Trading volume 7D">Volume (7d)</ng-container> <button [disabled]="(sort$ | async) === 'volumes'" class="btn btn-link btn-sm" (click)="sort('volumes')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
<th><ng-container i18n="Trades amount 7D">Trades (7d)</ng-container> <button [disabled]="(sort$ | async) === 'trades'" class="btn btn-link btn-sm" (click)="sort('trades')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
</thead>
<tbody *ngIf="tickers.value; else loadingTmpl">
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn;">
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.name }})</a></td>
<td>
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
<ng-template #fiat>
<span class="green-color">{{ ticker.last | currency: ticker.market.rsymbol }}</span>
</ng-template>
</td>
<td>
<app-fiat [value]="ticker.volume?.volume"></app-fiat>
</td>
<td>{{ ticker.volume?.num_trades ? ticker.volume?.num_trades : 0 }}</td>
</tr>
</tbody>
</table>
</div>
<br><br>
<h2 i18n="Latest Trades header">Latest Trades</h2>
<app-bisq-trades [trades$]="trades$"></app-bisq-trades>
</ng-container>
</div>
</div>
<ng-template #loadingTmpl>
<tr *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]">
<td *ngFor="let j of [1, 2, 3, 4]"><span class="skeleton-loader"></span></td>
</tr>
</ng-template>

View File

@@ -1,22 +0,0 @@
#volumeHolder {
height: 500px;
background-color: #000;
overflow: hidden;
display: flex;
justify-content: center;
}
.table {
max-width: 100%;
overflow: scroll;
}
.loadingVolumes {
position: relative;
top: 45%;
z-index: 100;
}
.container-info{
overflow-x: scroll;
}

View File

@@ -1,132 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable, combineLatest, BehaviorSubject, of } from 'rxjs';
import { map, share, switchMap } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
import { StateService } from '../../services/state.service';
import { WebsocketService } from '../../services/websocket.service';
import { BisqApiService } from '../bisq-api.service';
import { Trade } from '../bisq.interfaces';
@Component({
selector: 'app-bisq-dashboard',
templateUrl: './bisq-dashboard.component.html',
styleUrls: ['./bisq-dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BisqDashboardComponent implements OnInit {
tickers$: Observable<any>;
volumes$: Observable<any>;
trades$: Observable<Trade[]>;
sort$ = new BehaviorSubject<string>('trades');
allowCryptoCoins = ['usdc', 'l-btc', 'bsq'];
constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService,
public stateService: StateService,
private seoService: SeoService,
) { }
ngOnInit(): void {
this.seoService.setTitle($localize`:@@meta.title.bisq.markets:Markets`);
this.seoService.setDescription($localize`:@@meta.description.bisq.markets:Explore the full Bitcoin ecosystem with The Mempool Open Source Project&reg;. See Bisq market prices, trading activity, and more.`);
this.websocketService.want(['blocks']);
this.volumes$ = this.bisqApiService.getAllVolumesDay$()
.pipe(
map((volumes) => {
const data = volumes.map((volume) => {
return {
time: volume.period_start,
value: volume.volume,
};
});
const linesData = volumes.map((volume) => {
return {
time: volume.period_start,
value: volume.num_trades,
};
});
return {
data: data,
linesData: linesData,
};
})
);
const getMarkets = this.bisqApiService.getMarkets$().pipe(share());
this.tickers$ = combineLatest([
this.bisqApiService.getMarketsTicker$(),
getMarkets,
this.bisqApiService.getMarketVolumesByTime$('7d'),
])
.pipe(
map(([tickers, markets, volumes]) => {
const newTickers = [];
for (const t in tickers) {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
const pair = t.split('_');
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
continue;
}
}
const mappedTicker: any = tickers[t];
mappedTicker.pair_url = t;
mappedTicker.pair = t.replace('_', '/').toUpperCase();
mappedTicker.market = markets[t];
mappedTicker.volume = volumes[t];
mappedTicker.name = `${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lname : mappedTicker.market.rname} (${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lsymbol : mappedTicker.market.rsymbol}`;
newTickers.push(mappedTicker);
}
return newTickers;
}),
switchMap((tickers) => combineLatest([this.sort$, of(tickers)])),
map(([sort, tickers]) => {
if (sort === 'trades') {
tickers.sort((a, b) => (b.volume && b.volume.num_trades || 0) - (a.volume && a.volume.num_trades || 0));
} else if (sort === 'volumes') {
tickers.sort((a, b) => (b.volume && b.volume.volume || 0) - (a.volume && a.volume.volume || 0));
} else if (sort === 'name') {
tickers.sort((a, b) => a.name.localeCompare(b.name));
}
return tickers;
})
);
this.trades$ = combineLatest([
this.bisqApiService.getMarketTrades$('all'),
getMarkets,
])
.pipe(
map(([trades, markets]) => {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
trades = trades.filter((trade) => {
const pair = trade.market.split('_');
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);
});
}
return trades.map((trade => {
trade._market = markets[trade.market];
return trade;
}));
})
);
}
trackByFn(index: number) {
return index;
}
sort(by: string) {
this.sort$.next(by);
}
}

View File

@@ -1 +0,0 @@
<fa-icon [icon]="iconProp" [fixedWidth]="true" [ngStyle]="{ 'color': '#' + color }"></fa-icon>

View File

@@ -1,87 +0,0 @@
import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
import { IconPrefix, IconName } from '@fortawesome/fontawesome-common-types';
@Component({
selector: 'app-bisq-icon',
templateUrl: './bisq-icon.component.html',
styleUrls: ['./bisq-icon.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BisqIconComponent implements OnChanges {
@Input() txType: string;
iconProp: [IconPrefix, IconName] = ['fas', 'leaf'];
color: string;
constructor() { }
ngOnChanges() {
switch (this.txType) {
case 'UNVERIFIED':
this.iconProp[1] = 'question';
this.color = 'ffac00';
break;
case 'INVALID':
this.iconProp[1] = 'exclamation-triangle';
this.color = 'ff4500';
break;
case 'GENESIS':
this.iconProp[1] = 'rocket';
this.color = '25B135';
break;
case 'TRANSFER_BSQ':
this.iconProp[1] = 'retweet';
this.color = 'a3a3a3';
break;
case 'PAY_TRADE_FEE':
this.iconProp[1] = 'leaf';
this.color = '689f43';
break;
case 'PROPOSAL':
this.iconProp[1] = 'file-alt';
this.color = '6c8b3b';
break;
case 'COMPENSATION_REQUEST':
this.iconProp[1] = 'money-bill';
this.color = '689f43';
break;
case 'REIMBURSEMENT_REQUEST':
this.iconProp[1] = 'money-bill';
this.color = '04a908';
break;
case 'BLIND_VOTE':
this.iconProp[1] = 'eye-slash';
this.color = '07579a';
break;
case 'VOTE_REVEAL':
this.iconProp[1] = 'eye';
this.color = '4AC5FF';
break;
case 'LOCKUP':
this.iconProp[1] = 'lock';
this.color = '0056c4';
break;
case 'UNLOCK':
this.iconProp[1] = 'lock-open';
this.color = '1d965f';
break;
case 'ASSET_LISTING_FEE':
this.iconProp[1] = 'file-alt';
this.color = '6c8b3b';
break;
case 'PROOF_OF_BURN':
this.iconProp[1] = 'file-alt';
this.color = '6c8b3b';
break;
case 'IRREGULAR':
this.iconProp[1] = 'exclamation-circle';
this.color = 'ffd700';
break;
default:
this.iconProp[1] = 'question';
this.color = 'ffac00';
}
// @ts-ignore
this.iconProp = this.iconProp.slice();
}
}

View File

@@ -1,124 +0,0 @@
<div class="container-xl">
<br>
<div class="row row-cols-1 row-cols-md-2">
<div class="col mb-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title" i18n="bisq-dashboard.price-index-title">Bisq Price Index</h5>
<div class="big-fiat">
<span *ngIf="usdPrice$ | async as usdPrice; else loading">
<span [appColoredPrice]="usdPrice">{{ usdPrice | currency:'USD':'symbol':'1.2-2' }}</span>
</span>
</div>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title" i18n="bisq-dashboard.market-price-title">Bisq Market Price</h5>
<div class="big-fiat">
<span class="green-color" *ngIf="bisqMarketPrice; else loading">
<span [appColoredPrice]="bisqMarketPrice">{{ bisqMarketPrice | currency:'USD':'symbol':'1.2-2' }}</span>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col mb-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">US Dollar - BTC/USD</h5>
<div class="chart-container">
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
<app-lightweight-charts [height]="300" [data]="hlocData.hloc" [volumeData]="hlocData.volume" [precision]="2"></app-lightweight-charts>
</ng-container>
</div>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title" i18n="Bisq markets title">Bisq Trading Volume</h5>
<div class="chart-container">
<ng-container *ngIf="volumes$ | async as volumes; else loadingSpinner">
<app-lightweight-charts-area [height]="300" [data]="volumes.data" [lineData]="volumes.linesData"></app-lightweight-charts-area>
</ng-container>
</div>
</div>
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-md-2">
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
<div class="col mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">
<ng-template [ngIf]="stateService.env.BASE_MODULE === 'bisq'" [ngIfElse]="nonOfficialMarkets" i18n="Bisq All Markets">Markets</ng-template>
<ng-template #nonOfficialMarkets i18n="Bisq Bitcoin Markets">Bitcoin Markets</ng-template>
</h5>
<div class="table-container">
<table class="table table-borderless table-striped">
<thead>
<th><ng-container i18n>Currency</ng-container> <button [disabled]="(sort$ | async) === 'name'" class="btn btn-link btn-sm" (click)="sort('name')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
<th i18n>Price</th>
<th><ng-container i18n="Trades amount 7D">Trades (7d)</ng-container> <button [disabled]="(sort$ | async) === 'trades'" class="btn btn-link btn-sm" (click)="sort('trades')"><fa-icon [icon]="['fas', 'chevron-down']" [fixedWidth]="true"></fa-icon></button></th>
</thead>
<tbody *ngIf="tickers.value; else loadingTmpl">
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn;">
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.name }})</a></td>
<td>
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
<ng-template #fiat>
<span class="green-color">{{ ticker.last | currency: ticker.market.rsymbol }}</span>
</ng-template>
</td>
<td>{{ ticker.volume?.num_trades ? ticker.volume?.num_trades : 0 }}</td>
</tr>
</tbody>
</table>
</div>
<div class="text-center"><a href="" [routerLink]="['/markets' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div>
<div class="col mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center" i18n="Latest Trades header">Latest Trades</h5>
<app-bisq-trades [trades$]="trades$" view="small"></app-bisq-trades>
<div class="text-center"><a href="" [routerLink]="['/markets' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div>
</ng-container>
</div>
</div>
<ng-template #loadingTmpl>
<tr *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]">
<td *ngFor="let j of [1, 2, 3]"><span class="skeleton-loader"></span></td>
</tr>
</ng-template>
<ng-template #loadingSpinner>
<div class="text-center loadingGraphs">
<div class="spinner-border text-light"></div>
</div>
</ng-template>
<ng-template #loading>
<div class="skeleton-loader shorter"></div>
</ng-template>

View File

@@ -1,112 +0,0 @@
#volumeHolder {
height: 500px;
background-color: #000;
overflow: hidden;
display: flex;
justify-content: center;
}
.table {
max-width: 100%;
overflow: scroll;
}
.loadingGraphs {
position: relative;
top: 45%;
z-index: 100;
}
.table-container {
overflow: scroll;
scrollbar-width: none;
font-size: 13px;
&::-webkit-scrollbar {
display: none;
}
@media(min-width: 576px){
font-size: 16px;
}
thead th{
text-align: right;
&:first-child {
text-align: left;
}
&:nth-child(3) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
tr {
td {
text-align: right;
&:first-child {
text-align: left;
}
&:nth-child(3) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
}
}
.chart-container {
height: 300px;
}
.big-fiat {
color: #3bcc49;
font-size: 26px;
}
.card {
background-color: #1d1f31;
height: 100%;
}
.card-title {
color: #4a68b9;
font-size: 1rem;
}
.info-block {
float: left;
width: 350px;
line-height: 25px;
}
.progress {
display: inline-flex;
width: 100%;
background-color: #2d3348;
height: 1.1rem;
}
.bg-warning {
background-color: #b58800 !important;
}
.skeleton-loader {
max-width: 100%;
&.shorter {
max-width: 150px;
}
}
.more-padding {
padding: 1.25rem 2rem 1.25rem 2rem;
}
.graph-card {
height: 100%;
@media (min-width: 992px) {
height: 385px;
}
}

View File

@@ -1,193 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable, combineLatest, BehaviorSubject, of } from 'rxjs';
import { map, share, switchMap } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
import { StateService } from '../../services/state.service';
import { WebsocketService } from '../../services/websocket.service';
import { BisqApiService } from '../bisq-api.service';
import { Trade } from '../bisq.interfaces';
@Component({
selector: 'app-main-bisq-dashboard',
templateUrl: './bisq-main-dashboard.component.html',
styleUrls: ['./bisq-main-dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BisqMainDashboardComponent implements OnInit {
tickers$: Observable<any>;
volumes$: Observable<any>;
trades$: Observable<Trade[]>;
sort$ = new BehaviorSubject<string>('trades');
hlocData$: Observable<any>;
usdPrice$: Observable<number>;
isLoadingGraph = true;
bisqMarketPrice = 0;
allowCryptoCoins = ['usdc', 'l-btc', 'bsq'];
constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService,
public stateService: StateService,
private seoService: SeoService,
) { }
ngOnInit(): void {
this.seoService.resetTitle();
this.seoService.resetDescription();
this.websocketService.want(['blocks']);
this.usdPrice$ = this.stateService.conversions$.asObservable().pipe(
map((conversions) => conversions.USD)
);
this.volumes$ = this.bisqApiService.getAllVolumesDay$()
.pipe(
map((volumes) => {
const data = volumes.map((volume) => {
return {
time: volume.period_start,
value: volume.volume,
};
});
const linesData = volumes.map((volume) => {
return {
time: volume.period_start,
value: volume.num_trades,
};
});
return {
data: data,
linesData: linesData,
};
})
);
const getMarkets = this.bisqApiService.getMarkets$().pipe(share());
this.tickers$ = combineLatest([
this.bisqApiService.getMarketsTicker$(),
getMarkets,
this.bisqApiService.getMarketVolumesByTime$('7d'),
])
.pipe(
map(([tickers, markets, volumes]) => {
const newTickers = [];
for (const t in tickers) {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
const pair = t.split('_');
if (pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1) {
continue;
}
}
const mappedTicker: any = tickers[t];
mappedTicker.pair_url = t;
mappedTicker.pair = t.replace('_', '/').toUpperCase();
mappedTicker.market = markets[t];
mappedTicker.volume = volumes[t];
mappedTicker.name = `${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lname : mappedTicker.market.rname} (${mappedTicker.market.rtype === 'crypto' ? mappedTicker.market.lsymbol : mappedTicker.market.rsymbol}`;
newTickers.push(mappedTicker);
}
return newTickers;
}),
switchMap((tickers) => combineLatest([this.sort$, of(tickers)])),
map(([sort, tickers]) => {
if (sort === 'trades') {
tickers.sort((a, b) => (b.volume && b.volume.num_trades || 0) - (a.volume && a.volume.num_trades || 0));
} else if (sort === 'volumes') {
tickers.sort((a, b) => (b.volume && b.volume.volume || 0) - (a.volume && a.volume.volume || 0));
} else if (sort === 'name') {
tickers.sort((a, b) => a.name.localeCompare(b.name));
}
return tickers.slice(0, 10);
})
);
this.trades$ = combineLatest([
this.bisqApiService.getMarketTrades$('all'),
getMarkets,
])
.pipe(
map(([trades, markets]) => {
if (this.stateService.env.BASE_MODULE !== 'bisq') {
trades = trades.filter((trade) => {
const pair = trade.market.split('_');
return !(pair[1] === 'btc' && this.allowCryptoCoins.indexOf(pair[0]) === -1);
});
}
return trades.map((trade => {
trade._market = markets[trade.market];
return trade;
})).slice(0, 10);
})
);
this.hlocData$ = this.bisqApiService.getMarketsHloc$('btc_usd', 'day')
.pipe(
map((hlocData) => {
this.isLoadingGraph = false;
hlocData = hlocData.map((h) => {
h.time = h.period_start;
return h;
});
const hlocVolume = hlocData.map((h) => {
return {
time: h.time,
value: h.volume_right,
color: h.close > h.avg ? 'rgba(0, 41, 74, 0.7)' : 'rgba(0, 41, 74, 1)',
};
});
// Add whitespace
if (hlocData.length > 1) {
const newHloc = [];
newHloc.push(hlocData[0]);
const period = 86400;
let periods = 0;
const startingDate = hlocData[0].period_start;
let index = 1;
while (true) {
periods++;
if (hlocData[index].period_start > startingDate + period * periods) {
newHloc.push({
time: startingDate + period * periods,
});
} else {
newHloc.push(hlocData[index]);
index++;
if (!hlocData[index]) {
break;
}
}
}
hlocData = newHloc;
}
this.bisqMarketPrice = hlocData[hlocData.length - 1].close;
return {
hloc: hlocData,
volume: hlocVolume,
};
}),
);
}
trackByFn(index: number) {
return index;
}
sort(by: string) {
this.sort$.next(by);
}
}

View File

@@ -1,112 +0,0 @@
<div class="container-xl">
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
<ng-container *ngIf="currency$ | async as currency; else loadingSpinner">
<h1>{{ currency.market.rtype === 'crypto' ? currency.market.lname : currency.market.rname }} - {{ currency.pair }}</h1>
<div class="priceheader">
<ng-container *ngIf="currency.market.rtype === 'fiat'; else headerPriceCrypto"><span class="green-color">{{ hlocData.hloc[hlocData.hloc.length - 1].close | currency: currency.market.rsymbol }}</span></ng-container>
<ng-template #headerPriceCrypto>{{ hlocData.hloc[hlocData.hloc.length - 1].close | number: '1.' + currency.market.rprecision + '-' + currency.market.rprecision }} {{ currency.market.rsymbol }}</ng-template>
</div>
<form [formGroup]="radioGroupForm" class="mb-3 radio-form">
<div class="btn-group btn-group-toggle" name="radioBasic">
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('interval').value === 'half_hour'">
<input type="radio" [value]="'half_hour'" (click)="setFragment('half_hour')" formControlName="interval"> 30M
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('interval').value === 'hour'">
<input type="radio" [value]="'hour'" (click)="setFragment('hour')" formControlName="interval"> 1H
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('interval').value === 'half_day'">
<input type="radio" [value]="'half_day'" (click)="setFragment('half_day')" formControlName="interval"> 12H
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('interval').value === 'day'">
<input type="radio" [value]="'day'" (click)="setFragment('day')" formControlName="interval"> 1D
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('interval').value === 'week'">
<input type="radio" [value]="'week'" (click)="setFragment('week')" formControlName="interval"> 1W
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('interval').value === 'month'">
<input type="radio" [value]="'month'" (click)="setFragment('month')" formControlName="interval"> 1M
</label>
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('interval').value === 'year'">
<input type="radio" [value]="'year'" (click)="setFragment('year')" formControlName="interval"> 1Y
</label>
</div>
</form>
<div class="clearfix"></div>
<div id="graphHolder">
<div class="text-center loadingChart" [hidden]="!isLoadingGraph">
<div class="spinner-border text-light"></div>
</div>
<app-lightweight-charts [data]="hlocData.hloc" [volumeData]="hlocData.volume" [precision]="currency.market.rtype === 'crypto' ? currency.market.lprecision : currency.market.rprecision"></app-lightweight-charts>
</div>
<br>
<ng-container *ngIf="offers$ | async as offers; else loadingSpinner">
<div class="row row-cols-1 row-cols-md-2">
<ng-container *ngTemplateOutlet="offersList; context: { offers: offers.buys, direction: 'BUY', market: currency.market }"></ng-container>
<ng-container *ngTemplateOutlet="offersList; context: { offers: offers.sells, direction: 'SELL', market: currency.market }"></ng-container>
</div>
</ng-container>
<br><br>
<ng-container *ngIf="trades$ | async as trades; else loadingSpinner">
<h2 i18n="Latest Trades header">Latest Trades</h2>
<app-bisq-trades [trades$]="trades$" [market]="currency.market"></app-bisq-trades>
</ng-container>
</ng-container>
</ng-container>
</div>
<ng-template #offersList let-offers="offers" let-direction="direction", let-market="market">
<div class="col">
<h2>
<ng-template [ngIf]="direction === 'BUY'" [ngIfElse]="sellOffers" i18n="Bisq Buy Offers">Buy Offers</ng-template>
<ng-template #sellOffers i18n="Bisq Sell Offers">Sell Offers</ng-template>
</h2>
<div class="table-container">
<table class="table table-borderless table-striped">
<thead>
<th i18n>Price</th>
<th><ng-container *ngTemplateOutlet="amount; context: {$implicit: market.lsymbol }"></ng-container></th>
<th><ng-container *ngTemplateOutlet="amount; context: {$implicit: market.rsymbol }"></ng-container></th>
</thead>
<tbody>
<tr *ngFor="let offer of offers">
<td>
<ng-container *ngIf="market.rtype === 'fiat'; else priceCrypto"><span class="green-color">{{ offer.price | currency: market.rsymbol }}</span></ng-container>
<ng-template #priceCrypto>{{ offer.price | number: '1.2-' + market.rprecision }} <span class="symbol">{{ market.rsymbol }}</span></ng-template>
</td>
<td>
<ng-container *ngIf="market.ltype === 'fiat'; else amountCrypto"><span class="green-color">{{ offer.amount | currency: market.rsymbol }}</span></ng-container>
<ng-template #amountCrypto>{{ offer.amount | number: '1.2-' + market.lprecision }} <span class="symbol">{{ market.lsymbol }}</span></ng-template>
</td>
<td>
<ng-container *ngIf="market.rtype === 'fiat'; else volumeCrypto"><span class="green-color">{{ offer.volume | currency: market.rsymbol }}</span></ng-container>
<ng-template #volumeCrypto>{{ offer.volume | number: '1.2-' + market.rprecision }} <span class="symbol">{{ market.rsymbol }}</span></ng-template>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-template>
<ng-template #loadingSpinner>
<br>
<br>
<div class="text-center">
<div class="spinner-border text-light"></div>
</div>
</ng-template>
<ng-template #amount let-i i18n="Trade amount (Symbol)">Amount ({{ i }})</ng-template>

View File

@@ -1,46 +0,0 @@
.priceheader {
font-size: 24px;
@media(min-width: 576px){
float: left;
}
}
.radio-form {
@media(min-width: 576px){
float: right;
}
}
.loadingChart {
z-index: 100;
position: absolute;
top: 50%;
left: 50%;
}
#graphHolder {
height: 550px;
overflow: hidden;
}
.col {
&:last-child{
margin-top: 50px;
@media(min-width: 576px){
margin-top: 0px;
}
}
}
.table-container {
overflow: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
font-size: 13px;
@media(min-width: 576px){
font-size: 16px;
}
&::-webkit-scrollbar {
display: none;
}
}

View File

@@ -1,159 +0,0 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { combineLatest, merge, Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
import { WebsocketService } from '../../services/websocket.service';
import { BisqApiService } from '../bisq-api.service';
import { OffersMarket, Trade } from '../bisq.interfaces';
@Component({
selector: 'app-bisq-market',
templateUrl: './bisq-market.component.html',
styleUrls: ['./bisq-market.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BisqMarketComponent implements OnInit, OnDestroy {
hlocData$: Observable<any>;
currency$: Observable<any>;
offers$: Observable<OffersMarket>;
trades$: Observable<Trade[]>;
radioGroupForm: UntypedFormGroup;
defaultInterval = 'day';
isLoadingGraph = false;
constructor(
private websocketService: WebsocketService,
private route: ActivatedRoute,
private bisqApiService: BisqApiService,
private formBuilder: UntypedFormBuilder,
private seoService: SeoService,
private router: Router,
) { }
ngOnInit(): void {
this.radioGroupForm = this.formBuilder.group({
interval: [this.defaultInterval],
});
if (['half_hour', 'hour', 'half_day', 'day', 'week', 'month', 'year', 'auto'].indexOf(this.route.snapshot.fragment) > -1) {
this.radioGroupForm.controls.interval.setValue(this.route.snapshot.fragment, { emitEvent: false });
}
this.currency$ = this.bisqApiService.getMarkets$()
.pipe(
switchMap((markets) => combineLatest([of(markets), this.route.paramMap])),
map(([markets, routeParams]) => {
const pair = routeParams.get('pair');
const pairUpperCase = pair.replace('_', '/').toUpperCase();
this.seoService.setTitle($localize`:@@meta.title.bisq.market:Bisq market: ${pairUpperCase}`);
this.seoService.setDescription($localize`:@@meta.description.bisq.market:See price history, current buy/sell offers, and latest trades for the ${pairUpperCase} market on Bisq.`);
return {
pair: pairUpperCase,
market: markets[pair],
};
})
);
this.trades$ = this.route.paramMap
.pipe(
map(routeParams => routeParams.get('pair')),
switchMap((marketPair) => this.bisqApiService.getMarketTrades$(marketPair)),
);
this.offers$ = this.route.paramMap
.pipe(
map(routeParams => routeParams.get('pair')),
switchMap((marketPair) => this.bisqApiService.getMarketOffers$(marketPair)),
map((offers) => offers[Object.keys(offers)[0]])
);
this.hlocData$ = combineLatest([
this.route.paramMap,
merge(this.radioGroupForm.get('interval').valueChanges, of(this.radioGroupForm.get('interval').value)),
])
.pipe(
switchMap(([routeParams, interval]) => {
this.isLoadingGraph = true;
const pair = routeParams.get('pair');
return this.bisqApiService.getMarketsHloc$(pair, interval);
}),
map((hlocData) => {
this.isLoadingGraph = false;
hlocData = hlocData.map((h) => {
h.time = h.period_start;
return h;
});
const hlocVolume = hlocData.map((h) => {
return {
time: h.time,
value: h.volume_right,
color: h.close > h.avg ? 'rgba(0, 41, 74, 0.7)' : 'rgba(0, 41, 74, 1)',
};
});
// Add whitespace
if (hlocData.length > 1) {
const newHloc = [];
newHloc.push(hlocData[0]);
const period = this.getUnixTimestampFromInterval(this.radioGroupForm.get('interval').value); // temp
let periods = 0;
const startingDate = hlocData[0].period_start;
let index = 1;
while (true) {
periods++;
if (hlocData[index].period_start > startingDate + period * periods) {
newHloc.push({
time: startingDate + period * periods,
});
} else {
newHloc.push(hlocData[index]);
index++;
if (!hlocData[index]) {
break;
}
}
}
hlocData = newHloc;
}
return {
hloc: hlocData,
volume: hlocVolume,
};
}),
);
}
setFragment(fragment: string) {
this.router.navigate([], {
relativeTo: this.route,
queryParamsHandling: 'merge',
fragment: fragment
});
}
ngOnDestroy(): void {
this.websocketService.stopTrackingBisqMarket();
}
getUnixTimestampFromInterval(interval: string): number {
switch (interval) {
case 'minute': return 60;
case 'half_hour': return 1800;
case 'hour': return 3600;
case 'half_day': return 43200;
case 'day': return 86400;
case 'week': return 604800;
case 'month': return 2592000;
case 'year': return 31579200;
}
}
}

View File

@@ -1,86 +0,0 @@
<div class="container-xl">
<h1 style="float: left;" i18n="BSQ statistics header">BSQ statistics</h1>
<br>
<div class="clearfix"></div>
<div class="row">
<div class="col-md">
<table class="table table-borderless table-striped">
<tbody *ngIf="!isLoading; else loadingTemplate">
<tr>
<td class="td-width" i18n="BSQ existing amount">Existing amount</td>
<td>{{ (stats.minted - stats.burnt) | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
</tr>
<tr>
<td i18n="BSQ minted amount">Minted amount</td>
<td>{{ stats.minted | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
</tr>
<tr>
<td i18n="BSQ burnt amount">Burnt amount</td>
<td>{{ stats.burnt | number: '1.2-2' }} <span class="symbol">BSQ</span></td>
</tr>
<tr>
<td i18n="BSQ addresses">Addresses</td>
<td>{{ stats.addresses | number }}</td>
</tr>
<tr>
<td i18n="BSQ unspent transaction outputs">Unspent TXOs</td>
<td>{{ stats.unspent_txos | number }}</td>
</tr>
<tr>
<td i18n="BSQ spent transaction outputs">Spent TXOs</td>
<td>{{ stats.spent_txos | number }}</td>
</tr>
<tr>
<td i18n>Price</td>
<td><app-fiat [value]="price"></app-fiat></td>
</tr>
<tr>
<td i18n="BSQ token market cap">Market cap</td>
<td><app-fiat [value]="price * (stats.minted - stats.burnt)"></app-fiat></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md"></div>
</div>
</div>
<ng-template #loadingTemplate>
<tbody>
<tr>
<td class="td-width" i18n>Existing amount</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td i18n>Minted amount</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td i18n>Burnt amount</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td i18n>Addresses</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td i18n>Unspent TXOs</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td>Spent TXOs</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td i18n>Price</td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td i18n>Market cap</td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</ng-template>

View File

@@ -1,18 +0,0 @@
.td-width {
width: 250px;
}
@media (max-width: 767.98px) {
.td-width {
width: 175px;
}
}
.fiat {
display: block;
font-size: 13px;
@media (min-width: 768px) {
font-size: 14px;
display: inline-block;
}
}

View File

@@ -1,42 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { BisqApiService } from '../bisq-api.service';
import { BisqStats } from '../bisq.interfaces';
import { SeoService } from '../../services/seo.service';
import { StateService } from '../../services/state.service';
import { WebsocketService } from '../../services/websocket.service';
@Component({
selector: 'app-bisq-stats',
templateUrl: './bisq-stats.component.html',
styleUrls: ['./bisq-stats.component.scss']
})
export class BisqStatsComponent implements OnInit {
isLoading = true;
stats: BisqStats;
price: number;
constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService,
private seoService: SeoService,
private stateService: StateService,
) { }
ngOnInit() {
this.websocketService.want(['blocks']);
this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`);
this.seoService.setDescription($localize`:@@meta.description.bisq.stats:See high-level stats on the BSQ economy: supply metrics, number of addresses, BSQ price, market cap, and more.`);
this.stateService.bsqPrice$
.subscribe((bsqPrice) => {
this.price = bsqPrice;
});
this.bisqApiService.getStats$()
.subscribe((stats) => {
this.isLoading = false;
this.stats = stats;
});
}
}

View File

@@ -1,46 +0,0 @@
<div class="table-container">
<table class="table table-borderless table-striped">
<thead>
<th i18n>Date</th>
<th *ngIf="view === 'all'" i18n>Price</th>
<th><ng-container *ngTemplateOutlet="amount; context: {$implicit: 'BTC' }"></ng-container></th>
<th>
<ng-template [ngIf]="market" [ngIfElse]="noMarket"><ng-container *ngTemplateOutlet="amount; context: {$implicit: market.lsymbol === 'BTC' ? market.rsymbol : market.lsymbol }"></ng-container></ng-template>
<ng-template #noMarket i18n>Amount</ng-template>
</th>
</thead>
<tbody *ngIf="(trades$ | async) as trades; else loadingTmpl">
<tr *ngFor="let trade of trades;">
<td>
&lrm;{{ trade.trade_date | date:'yyyy-MM-dd HH:mm' }}
</td>
<td *ngIf="view === 'all'">
<ng-container *ngIf="(trade._market || market).rtype === 'fiat'; else priceCrypto"><span class="green-color">{{ trade.price | currency: (trade._market || market).rsymbol }}</span></ng-container>
<ng-template #priceCrypto>{{ trade.price | number: '1.2-' + (trade._market || market).rprecision }} <span class="symbol">{{ (trade._market || market).rsymbol }}</span></ng-template>
</td>
<ng-container *ngTemplateOutlet="(trade._market || market).rsymbol === 'BTC' ? tradeVolume : tradeAmount"></ng-container>
<ng-container *ngTemplateOutlet="(trade._market || market).rsymbol === 'BTC' ? tradeAmount : tradeVolume"></ng-container>
<ng-template #tradeAmount>
<td>
<ng-container *ngIf="(trade._market || market).ltype === 'fiat'; else amountCrypto"><span class="green-color">{{ trade.amount | currency: (trade._market || market).rsymbol }}</span></ng-container>
<ng-template #amountCrypto>{{ trade.amount | number: '1.2-' + (trade._market || market).lprecision }} <span class="symbol">{{ (trade._market || market).lsymbol }}</span></ng-template>
</td>
</ng-template>
<ng-template #tradeVolume>
<td>
<ng-container *ngIf="(trade._market || market).rtype === 'fiat'; else volumeCrypto"><span class="green-color">{{ trade.volume | currency: (trade._market || market).rsymbol }}</span></ng-container>
<ng-template #volumeCrypto>{{ trade.volume | number: '1.2-' + (trade._market || market).rprecision }} <span class="symbol">{{ (trade._market || market).rsymbol }}</span></ng-template>
</td>
</ng-template>
</tr>
</tbody>
</table>
</div>
<ng-template #loadingTmpl>
<tr *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]">
<td *ngFor="let j of loadingColumns"><span class="skeleton-loader"></span></td>
</tr>
</ng-template>
<ng-template #amount let-i i18n="Trade amount (Symbol)">Amount ({{ i }})</ng-template>

View File

@@ -1,38 +0,0 @@
.table-container {
overflow: scroll;
scrollbar-width: none;
font-size: 13px;
&::-webkit-scrollbar {
display: none;
}
@media(min-width: 576px){
font-size: 16px;
}
thead th{
text-align: right;
&:first-child{
text-align: left;
}
&:nth-child(2) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
tr {
td {
text-align: right;
&:first-child{
text-align: left;
}
&:nth-child(2) {
display: none;
@media(min-width: 1100px){
display: table-cell;
}
}
}
}
}

View File

@@ -1,22 +0,0 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
@Component({
selector: 'app-bisq-trades',
templateUrl: './bisq-trades.component.html',
styleUrls: ['./bisq-trades.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BisqTradesComponent implements OnChanges {
@Input() trades$: Observable<any>;
@Input() market: any;
@Input() view: 'all' | 'small' = 'all';
loadingColumns = [1, 2, 3, 4];
ngOnChanges() {
if (this.view === 'small') {
this.loadingColumns = [1, 2, 3];
}
}
}

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