Compare commits

...

235 Commits

Author SHA1 Message Date
wiz
5d360d4156 Merge pull request #1003 from mempool/simon/database-migration-feature
Automated database creation and migration
2021-12-23 21:50:10 +00:00
softsimon
91e30fbc3c Merge branch 'master' into simon/database-migration-feature
# Conflicts:
#	backend/src/index.ts
2021-12-24 00:26:33 +04:00
wiz
5b22e2a000 Merge pull request #1010 from mempool/simon/liquid-icons-api
Liquid icons api
2021-12-23 12:28:54 +00:00
softsimon
533653e54a Change Asset Icon API example to only show HTML 2021-12-23 15:35:17 +04:00
wiz
3dc0dc13ad Merge pull request #1038 from nymkappa/feature/increase-resolution-24h
Switch the 24h chart to 1 min data ticks
2021-12-22 18:33:32 +00:00
softsimon
e332789afc Bumping mempool.js to 2.3.0-dev1 and removing unused tsc build step 2021-12-22 19:33:10 +04:00
nymkappa
e4a9fd06b4 Switch the 24h chart to 1 min data ticks 2021-12-22 23:01:32 +09:00
softsimon
5845f2380e Adding sync external assets feature. 2021-12-21 02:00:50 +04:00
softsimon
c29311d831 Upgrading mempool-js with separated Liquid and Bisq 2021-12-20 23:48:26 +04:00
softsimon
252db109bc Adding icons.json to .gitignore 2021-12-20 04:01:40 +04:00
softsimon
b1c9334119 Changing API path and updating API Docs for asset icons. 2021-12-20 04:01:04 +04:00
wiz
ab04247726 Merge pull request #1033 from mempool/simon/extract-i18n
Extracting i18n string
2021-12-19 19:06:11 +00:00
softsimon
e94a85b989 Extracting i18n string 2021-12-19 22:57:31 +04:00
softsimon
a4569788f8 Liquid icons api 2021-12-19 22:09:49 +04:00
wiz
b455814e90 Merge pull request #1027 from hunicus/change-docs-layout
Revamp docs layout
2021-12-19 17:48:26 +00:00
softsimon
7afd0f3fe7 Merge pull request #1032 from mempool/wiz/update-about-page-links
Update links on About page
2021-12-19 21:36:10 +04:00
hunicus
a2a85469cf Streamline api-docs-navs conditionals 2021-12-19 12:29:52 -05:00
wiz
94488a6029 Merge pull request #1031 from mempool/wiz/update-issue-templates
Update GitHub issue templates to redirect support requests to chat
2021-12-19 17:02:57 +00:00
wiz
8e4829146a Rename About page section: Project Staff -> Project Members 2021-12-20 01:54:06 +09:00
wiz
08f185525c Update About page chat links: replace telegram with matrix 2021-12-20 01:52:58 +09:00
wiz
d6b00fe39e Update GitHub issue templates to redirect support requests to chat 2021-12-20 01:31:29 +09:00
softsimon
cec3baeaa4 Merge pull request #1024 from nymkappa/feature/blocks-mouse-scroll
User can drag the blockchain blocks horizontally with the mouse
2021-12-19 12:59:04 +04:00
nymkappa
6e59733cac User can drag the blockchain blocks horizontally with the mouse 2021-12-19 15:20:21 +09:00
hunicus
c5b705ede7 Adjust bisq cypress tests 2021-12-17 16:22:16 -05:00
hunicus
2819e24efe Remove unnecessary file change 2021-12-17 15:08:21 -05:00
hunicus
5f9bc4497a Customize mobile nav button appearance point
Since there are different numbers of topics across
bitcoin, liquid, bisq, faq, etc.
2021-12-17 14:42:21 -05:00
hunicus
086b14e816 Add various ux improvements for mobile doc menu 2021-12-17 11:42:54 -05:00
hunicus
958bfe6d25 Separate docs-nav into new component
Since same markup will be needed for desktop
and mobile menus.
2021-12-16 19:11:54 -05:00
hunicus
e01ab449cf Add skeleton for mobile docs nav 2021-12-16 18:44:39 -05:00
softsimon
eeb0f403a3 Merge pull request #1016 from knorrium/add_failing_test_for_liquid_unconfidential_address
Add failing test for Liquid unconfidential address layout issue
2021-12-16 23:01:24 +04:00
hunicus
9a18019d9d Add :before element for space before anchors
@angular-router's `scrollOffset` property seems to be
global, so it might mess up something else, and it also
wasn't taking effect when navigating directly to an
anchor anyway (i.e. from browser's address bar).
2021-12-16 13:20:30 -05:00
Felipe Knorr Kuhn
bcbc60b456 Fix test assertion 2021-12-16 09:31:02 -08:00
Felipe Knorr Kuhn
0d14c30892 Merge remote-tracking branch 'origin/master' into add_failing_test_for_liquid_unconfidential_address 2021-12-16 09:14:46 -08:00
hunicus
5d8c970351 Update anchor links and add on-page links 2021-12-16 11:30:03 -05:00
hunicus
89fede9e48 Fix inconsistencies in api-docs markup 2021-12-16 09:54:07 -05:00
hunicus
f8a54784d0 Improve styling + switch section headings for tags 2021-12-16 09:54:07 -05:00
hunicus
010381aac4 Convert accordions to plain html 2021-12-16 08:46:51 -05:00
wiz
49c30c7237 Merge pull request #1018 from mempool/simon/liquid-address-overflow–fix
UX: Fixing overflowing unconfidential Liquid address
2021-12-16 07:39:26 +00:00
wiz
7ba0055c61 Merge pull request #1019 from mempool/simon/witness-interface-type-fix
Correcting minor interface typing error
2021-12-16 07:38:57 +00:00
wiz
72b631a4dc Merge pull request #1017 from mempool/simon/restoring-fee-distribution-graph
Revert "Remove dead code FeeDistributionGraphComponent"
2021-12-16 07:38:31 +00:00
hunicus
1a8fd23b05 Add links and styling to fixed desktop docs nav 2021-12-16 00:04:47 -05:00
Felipe Knorr Kuhn
b9a2143a5a Merge remote-tracking branch 'origin/master' into add_failing_test_for_liquid_unconfidential_address 2021-12-15 20:16:22 -08:00
hunicus
3ae46e6ba1 Make desktop docs-nav sticky on scroll 2021-12-15 22:57:10 -05:00
softsimon
815fb62e7d Correcting minor interface typing error 2021-12-16 04:53:27 +04:00
softsimon
7a19da560e UX: Fixing overflowing unconfidential Liquid address
fixes #1013
2021-12-16 04:49:24 +04:00
softsimon
43a1af4509 Revert "Remove dead code FeeDistributionGraphComponent"
This reverts commit 37722fe165.
2021-12-15 23:53:12 +04:00
hunicus
40f1949654 Create skeleton layout for desktop docs nav
This includes switching to a 2-column layout
(1 for nav and 1 for content) and moving footer
links to docs component so floats can be cleared
properly.
2021-12-15 13:17:37 -05:00
softsimon
cf1471acca Merge pull request #1015 from knorrium/add_tests_for_rbf_txs
Add tests for RBF txs
2021-12-15 21:28:45 +04:00
Felipe Knorr Kuhn
a2a69e522c Merge remote-tracking branch 'origin/master' into add_failing_test_for_liquid_unconfidential_address 2021-12-15 08:51:08 -08:00
Felipe Knorr Kuhn
4939941c88 Merge remote-tracking branch 'origin/master' into add_tests_for_rbf_txs 2021-12-15 08:50:28 -08:00
softsimon
a643f50016 Merge pull request #1012 from mempool/simon/cpfp-button-overlap-fix
Correcting CPFP button position on mobile
2021-12-15 13:11:04 +04:00
softsimon
836444a1c5 Merge pull request #1011 from mempool/simon/rbf-transaction-ux-bug
Fixing broken RBF alert
2021-12-15 13:10:31 +04:00
Felipe Knorr Kuhn
24d4b643e5 Add failing test for Liquid unconfidential address layout issue 2021-12-15 00:02:21 -08:00
Felipe Knorr Kuhn
7f045ae5b9 Add tests to detect layout issues in RBF transactions 2021-12-14 23:21:59 -08:00
Felipe Knorr Kuhn
96a2c8ab4e Add methods to detect overlapping components 2021-12-14 23:21:40 -08:00
Felipe Knorr Kuhn
47103d85d3 Update mocked ws tests to use the new syntax 2021-12-14 23:19:10 -08:00
Felipe Knorr Kuhn
0af5d733c4 Small refactor to support mocking RBF txs 2021-12-14 23:16:13 -08:00
Felipe Knorr Kuhn
bc7bbf5fe5 Fix formatting 2021-12-14 23:15:33 -08:00
Felipe Knorr Kuhn
4bc141cd67 Add fixture for RBF transactions 2021-12-14 23:14:55 -08:00
softsimon
1c6b3a46c6 Correcting CPFP button position on mobile
fixes #1006
2021-12-15 02:16:45 +04:00
softsimon
b41e32915f Fixing broken RBF alert
fixes #516
2021-12-15 01:15:16 +04:00
wiz
adb5bfe93f Merge pull request #1009 from mempool/wiz/tweak-xaxis-label-timestamp-format
Tweak the graph x-axis label date formatting for better i18n
2021-12-14 09:48:37 +00:00
wiz
f4b7bbc91c Tweak the graph x-axis label date formatting for better i18n 2021-12-14 18:25:48 +09:00
wiz
687b4af272 Merge pull request #1008 from mempool/wiz/blockstream
Add Blockstream as Enterprise Sponsor
2021-12-14 09:04:20 +00:00
wiz
e67f552fbd Merge pull request #1004 from jorisvial/bug/improve-x-axis-intervals
Improve x-axis labels and graph data ticks
2021-12-14 07:50:14 +00:00
nymkappa
28d3f190ff Update graph tick intervals - Hide label in zoom component - Show hour on 1y graphs 2021-12-14 16:33:17 +09:00
softsimon
2281116504 Automated database creation and migration
fixes #1002
2021-12-13 11:32:04 +04:00
nymkappa
92d745168c Doubled the data points for 1W and 3Y to improve resolution 2021-12-13 14:31:34 +09:00
nymkappa
cf0af20947 Hide xaxis label overlapping - Show current day/month/year below the chart for self better self containing overview 2021-12-13 14:27:05 +09:00
nymkappa
aee319ed51 Initialize graphWindowPreference in localstorage properly 2021-12-13 11:56:24 +09:00
wiz
dc5ced416d Add Blockstream as Enterprise Sponsor 2021-12-12 04:24:18 +09:00
nymkappa
c9f5002dc2 Use avg() mysql value for timespan between [24h, 6m] 2021-12-11 19:15:20 +09:00
nymkappa
6e4985602e Increase the number of data to be as close as possible from prod while keeping rounded intervals 2021-12-11 17:27:52 +09:00
nymkappa
11577842a2 Refactor tooltip formatting into common file and switch to native js localization 2021-12-11 17:10:55 +09:00
wiz
1cc8a9469a Merge pull request #1001 from mempool/simon/i18n-extraction
Extracting updated i18n strings
2021-12-11 06:43:39 +00:00
nymkappa
7e7dbdbaf2 Remove test code 2021-12-11 15:43:20 +09:00
nymkappa
fb152b6d7b Merge branch 'master' into bug/improve-x-axis-intervals 2021-12-11 15:32:10 +09:00
nymkappa
9e8a741d97 Apply proper datetime format according to choosen time scale and force 2h windowPreference in the dashboard 2021-12-11 15:26:59 +09:00
nymkappa
2b0d543ce7 Delete unused code 2021-12-11 15:26:14 +09:00
nymkappa
5e729373bb Use date interval so we leave mysql handle the number of days in a month etc 2021-12-11 15:26:02 +09:00
nymkappa
41f3f0ab46 Fix graph data for incoming transaction graphs 2021-12-11 10:38:13 +09:00
nymkappa
37722fe165 Remove dead code FeeDistributionGraphComponent 2021-12-11 00:24:10 +09:00
nymkappa
cbd187d06f Use time for xAxis type and fix the mempool tooltip accordingly 2021-12-11 00:04:20 +09:00
softsimon
833dd3ef9d Extracting updated i18n strings 2021-12-10 11:22:25 +04:00
softsimon
bd19496350 Merge pull request #1000 from knorrium/liquid_peg_tests
Add Liquid peg in and peg out tests
2021-12-10 11:19:28 +04:00
Felipe Knorr Kuhn
ec420d8f3e Disable Chrome security to allow Cypress to navigate out of localhost 2021-12-09 15:49:15 -08:00
Felipe Knorr Kuhn
9510c7d2ea Add tests for Liquid peg in and peg out addresses 2021-12-09 15:48:58 -08:00
wiz
92d2eb9727 Merge pull request #999 from mempool/simon/documentation-i18n-title 2021-12-09 14:22:50 +00:00
softsimon
72f7284311 Documentation i18n title 2021-12-09 17:49:36 +04:00
nymkappa
2b3463519a Format date properly according to the chosen time scale 2021-12-09 22:29:40 +09:00
nymkappa
00352d7e36 Use "natural" intervals for x-axis in charts 2021-12-09 22:04:23 +09:00
wiz
63e799b225 Merge pull request #998 from mempool/simon/transifex-pull
Pulling from Transifex
2021-12-09 13:04:20 +00:00
wiz
3c1e264464 Merge pull request #994 from hunicus/move-api-docs
Move /api to /docs/api
2021-12-09 12:56:46 +00:00
hunicus
ff148f15c4 Exclude websocket tab on bisq 2021-12-09 07:46:06 -05:00
softsimon
a5c7f152aa Pulling from Transifex 2021-12-09 16:42:41 +04:00
hunicus
f0be19409f Fix relative links on docs tabs 2021-12-09 07:11:04 -05:00
hunicus
936a306dc4 Remove extra padding in websocket api docs tab 2021-12-09 07:01:58 -05:00
hunicus
8ff5748368 Make nav url relative to fix non-mainnet links 2021-12-09 07:01:46 -05:00
softsimon
271726bf58 Merge pull request #996 from mempool/wiz/fix-upgrade-script-for-simon
Tweak upgrade script to search for remote origin branches
2021-12-09 14:29:28 +04:00
wiz
65e11c1aa1 Merge pull request #997 from mempool/simon/backend-libs-upgrade
Upgrading backend libraries
2021-12-09 10:27:59 +00:00
softsimon
0ccc992e4b Merge pull request #995 from hunicus/fix-windows-graph
Fix graph issue in #992
2021-12-09 14:17:51 +04:00
wiz
52397cb341 Merge pull request #993 from mempool/simon/angular-13
Upgrading frontend to Angular 13
2021-12-09 10:16:54 +00:00
softsimon
f7a9b691a8 Upgrading backend libraries 2021-12-09 13:24:29 +04:00
wiz
8f8c9c1fbc Merge pull request #986 from mempool/simon/liquid-peg-in-out-links-fix
Fixing broken Liquid peg-in and peg-out links
2021-12-09 09:21:41 +00:00
wiz
9e8ee4fedc Tweak upgrade script to search for remote origin branches 2021-12-09 18:12:06 +09:00
hunicus
30ffc0b56e Fix graph issues in #992 2021-12-08 17:50:02 -05:00
softsimon
053fb4d1b2 Merge pull request #991 from knorrium/add_nvmrc
Add .nvmrc file
2021-12-08 23:06:30 +04:00
softsimon
53cfda533d Merge pull request #989 from knorrium/update_cypress_deps
Update Cypress dependencies
2021-12-08 22:57:50 +04:00
softsimon
2d557195de Merge pull request #988 from knorrium/pin_node_version_on_gha
Pin node version on e2e GHA
2021-12-08 22:57:24 +04:00
softsimon
ed9b46bae0 Upgrading frontend to Angular 13 2021-12-08 22:08:20 +04:00
hunicus
23c9bf489a Move /api to /docs/api
Also make API docs responsive so they can be viewed
on mobile.

REST docs are at /docs/api/rest and WebSocket docs
are at /docs/api/websocket.
2021-12-08 12:37:42 -05:00
softsimon
7c89dde5d4 Merge pull request #983 from mempool/wiz/fix-about-page-staff-string
Fix string for Project Staff on About page
2021-12-08 14:36:20 +04:00
softsimon
87ca5b85d7 Fixing broken Liquid peg-in and peg-out links
fixes #977
2021-12-08 11:50:51 +04:00
Felipe Knorr Kuhn
a2a43f4647 Add .nvmrc to set node to 16.10.0 when changing into the project 2021-12-07 22:55:10 -08:00
Felipe Knorr Kuhn
9ac77f7113 Update Cypress dependencies 2021-12-07 22:25:10 -08:00
Felipe Knorr Kuhn
b6215dd54f Enable dependency caching on e2e GHA 2021-12-07 21:27:22 -08:00
Felipe Knorr Kuhn
69d5cfe98e Use node 16.10.0 on the e2e GHA 2021-12-07 21:22:21 -08:00
wiz
73d9cd7ab7 Merge pull request #981 from mempool/wiz/fix-npm-install-prod
Move npm deps in package.json to fix `npm install --prod`
2021-12-08 04:24:48 +00:00
wiz
84a6d29914 Fix i18n identifier for About Page -> Project Staff string
Co-authored-by: softsimon <softsimon@users.noreply.github.com>
2021-12-08 04:16:34 +00:00
wiz
4633164f99 Merge pull request #984 from mempool/wiz/update-readme-for-v2.3.0
Update README for upcoming v2.3.0 release
2021-12-08 04:15:32 +00:00
wiz
b22b63c9a2 Update README for upcoming v2.3.0 release 2021-12-08 00:04:24 +09:00
wiz
40cd2a1ac3 Fix string for Project Staff on About page 2021-12-07 23:29:17 +09:00
wiz
e28f5cc403 Merge pull request #982 from mempool/simon/core-contributors
Adding Core Contributors on the About page
2021-12-07 14:16:44 +00:00
softsimon
1594fed853 Adding Core Contributors on the About page 2021-12-07 18:00:49 +04:00
wiz
41c61ef506 Move npm deps in package.json to fix npm install --prod 2021-12-07 10:42:06 +00:00
wiz
53b0bc0e48 Merge pull request #980 from knorrium/docker_node_16_10_0
Set Dockerfiles to use Node v16.10.0
2021-12-07 08:04:53 +00:00
Felipe Knorr Kuhn
e00234dfb9 Set Dockerfiles to use Node v16.10.0 2021-12-06 23:52:23 -08:00
wiz
8412e28073 Merge pull request #974 from mempool/simon/taproot-display-missing-script
Display missing taproot inner script
2021-12-07 07:26:29 +00:00
softsimon
10a110e682 Display missing taproot inner script
fixes #973
2021-12-06 00:27:14 +04:00
softsimon
1e40ec4fd0 Merge pull request #970 from mempool/wiz/center-enterprise-sponsor-logos
Center the Enterprise Sponsor logos on About page
2021-12-03 12:08:06 +04:00
wiz
9998a64fa5 Center the Enterprise Sponsor logos on About page 2021-12-03 16:55:56 +09:00
wiz
d1e3c55a28 Merge pull request #967 from mempool/wiz/rename-square-to-spiral 2021-12-03 06:26:35 +00:00
softsimon
047220bd32 Merge pull request #968 from mempool/i18n/20211203-update-from-transifex
Update i18n strings from Transifex
2021-12-03 10:25:19 +04:00
wiz
1ce05a3ac9 Merge pull request #961 from mempool/simon/remodeling-fee-bands
Remodeling how historical fees are stored and presented.
2021-12-03 02:37:33 +00:00
wiz
0271584b69 Update i18n strings from Transifex 2021-12-03 08:54:13 +09:00
wiz
01da46daca Square -> Spiral 🌀 2021-12-03 07:30:24 +09:00
wiz
73f558db6e Merge pull request #963 from mempool/simon/liquid-handle-database-disabled 2021-11-30 10:50:19 +00:00
softsimon
feb8e35ec3 Handle database disabled config when running Liquid
fixes #962
2021-11-30 10:59:10 +04:00
softsimon
79e44479e9 Remodeling how historical fees are stored and presented.
fixes #908
2021-11-29 23:31:01 +04:00
wiz
9a39d3207f Merge pull request #959 from mempool/simon/lbtc-loading-spinner
Adding missing loading spinner to L-BTC peg graph
2021-11-28 12:13:23 +00:00
softsimon
7e55e836fd Adding missing loading spinner to L-BTC peg graph
fixes #955
2021-11-28 15:44:42 +04:00
softsimon
571bf3fa64 Merge pull request #957 from mempool/simon/merging-duplicate-i18n-strings
Merged block header and markets i18n strings
2021-11-28 14:57:43 +04:00
softsimon
0b1cf052a8 Merged block header, markets and genesis i18n strings 2021-11-28 14:55:17 +04:00
wiz
b301840bb8 Merge pull request #956 from mempool/simon/i18n-extract-nov28
Extracting i18n from templates
2021-11-28 09:31:56 +00:00
softsimon
a0e5a79ec4 Extracting i18n from templates 2021-11-28 13:23:27 +04:00
wiz
8da89230c9 Merge pull request #944 from mempool/simon/chart-loading-spinner
Moving chart loading spinner to chart component
2021-11-28 08:19:52 +00:00
wiz
2408e81537 Merge pull request #954 from mempool/simon/transaction-pages-and-loading
Fixing Bisq transactions page and skeleton loaders
2021-11-28 08:16:36 +00:00
softsimon
9377d80dbb Fixing Bisq transactions page and skeleton loaders
fixes #953
2021-11-27 13:06:57 +04:00
softsimon
8c200bc0e4 Merge pull request #950 from MiguelMedeiros/fix-time-span-styles
Fix time span menu styles.
2021-11-23 14:13:33 +04:00
Miguel Medeiros
35ca8c846b fix: time span menu media queries styles. 2021-11-22 20:29:10 -03:00
softsimon
4b1acfc77e Skip the skeleton loader tests 2021-11-20 00:34:59 +04:00
softsimon
8569523b89 Moving chart loading spinner to chart component
fixes #885
2021-11-19 00:10:12 +04:00
wiz
9bef7da210 Merge pull request #881 from mempool/simon/tx-push-endpoint
Post TX API for HTML forms
2021-11-18 22:40:55 +09:00
softsimon
6f6b2a4355 Post TX API for HTML forms 2021-11-18 17:25:48 +04:00
softsimon
783530b3de Merge pull request #943 from mempool/wiz/20211118-update-privacy-policy
Delete nginx logs after 10 days and explain this in our Privacy Policy
2021-11-18 16:37:58 +04:00
wiz
36777a8c5b Clarify webserver logging and analytics in our Privacy Policy 2021-11-18 17:05:34 +09:00
wiz
638d6d896a Update newsyslog-mempool-nginx.conf for all nginx log paths 2021-11-18 15:28:44 +09:00
wiz
2afb9d359f Merge pull request #942 from mempool/i18n/enable-thai
Enable i18n locale Thai (th)
2021-11-18 14:41:04 +09:00
wiz
60a8d0ce6e Update i18n translations for Thai (th) 2021-11-18 14:29:36 +09:00
wiz
e3aeb784ad Merge pull request #935 from MiguelMedeiros/fix-tooltip-opreturn
Fix: op_return tooltip position.
2021-11-17 22:58:29 +09:00
wiz
8124274880 Credit Gusb3ll as Thai translator 2021-11-17 22:18:38 +09:00
wiz
836f0f065d Enable i18n locale Thai (th) 2021-11-17 21:03:35 +09:00
wiz
062fdb3658 Don't change cursor when hovering over OP_RETURN 2021-11-17 20:06:24 +09:00
wiz
7cfbd2c70d Merge pull request #940 from mempool/simon/empty-mempool-block-position
Fix for empty mempool block position
2021-11-17 19:40:16 +09:00
wiz
5b9ae2eaf5 Merge pull request #941 from mempool/simon/hide-early-difficulty-adjustment
Handle recent difficulty adjustment estimate gracefully
2021-11-17 19:29:48 +09:00
softsimon
d534c42c47 Handle recent difficulty adjustment estimate gracefully
fixes #927
2021-11-17 13:46:48 +04:00
softsimon
15a0644bd1 Fix for empty mempool block position
fixes #938
2021-11-17 12:37:40 +04:00
wiz
6ad4e655ea Merge pull request #910 from mempool/simon/liquid-fee-ranges
Display lower <1 s/vB fee rate tiers for Liquid
2021-11-17 17:01:29 +09:00
softsimon
598920d6a9 Merge pull request #928 from knorrium/fix_local_dev_proxy
Fix local dev proxy
2021-11-17 09:38:10 +04:00
Miguel Medeiros
d816e67ce4 fix: op_return tooltip position. 2021-11-16 18:03:43 -03:00
wiz
662aafff57 Merge pull request #930 from mempool/simon/more-transaction-details
Adding version and locktime to transaction details
2021-11-16 06:18:01 +09:00
softsimon
cfec9345c8 Merge pull request #932 from MiguelMedeiros/fix-empty-mempool-blocks
Fix: mempool empty block.
2021-11-16 00:26:15 +04:00
Miguel Medeiros
9cb203f98a ref: remove function to better performance 2021-11-15 17:10:54 -03:00
Miguel Medeiros
3bab50a43b fix: blocksfull variable. 2021-11-15 16:38:28 -03:00
Miguel Medeiros
0639ce9b07 fix: mempool empty block. 2021-11-15 14:16:27 -03:00
softsimon
34c9ca4197 Adding version and locktime to transaction details
fixes #929
2021-11-15 18:05:28 +04:00
Felipe Knorr Kuhn
14d015a904 Set the local target to use the new local proxy config 2021-11-14 15:41:28 -08:00
Felipe Knorr Kuhn
38412753fe Add new local server proxy config 2021-11-14 15:40:45 -08:00
wiz
475f9344a0 Merge pull request #923 from mempool/simon/taproot-activation-countdown
Adding taproot countdown
2021-11-13 15:19:42 +09:00
softsimon
d9cf6a2604 Update frontend/src/app/app.constants.ts
Co-authored-by: Vojtěch Strnad <43024885+vostrnad@users.noreply.github.com>
2021-11-13 10:04:38 +04:00
softsimon
9eb159617f Adding taproot countdown 2021-11-12 21:07:57 +04:00
softsimon
2dc930fad2 Merge pull request #912 from MiguelMedeiros/taproot-activation-fireworks
Add special blocks animation: fireworks.
2021-11-12 13:30:54 +04:00
softsimon
c478478f86 Merge pull request #922 from knorrium/update_test_address
Update mainnet partial address tests
2021-11-12 10:23:35 +04:00
Felipe Knorr Kuhn
7a07f67be5 Update mainnet partial address tests 2021-11-11 21:56:19 -08:00
Miguel Medeiros
a3de75ebf4 Add mempool special block animation. 2021-11-11 16:04:00 -03:00
Miguel Medeiros
46a2854f67 Add taproot activation fireworks. 2021-11-11 16:04:00 -03:00
softsimon
033f066abf Merge pull request #898 from MiguelMedeiros/fix-translations-strings-tooltip-echarts
Localization: Add localize strings at echarts tooltip.
2021-11-11 15:28:49 +04:00
softsimon
97244c7b35 Merge pull request #921 from MiguelMedeiros/fix-button-menu-styles
Fix menu button styles.
2021-11-11 00:02:42 +04:00
Miguel Medeiros
3d92369ed6 fix: menu button styles. 2021-11-10 12:18:29 -03:00
wiz
2bee46951c Merge pull request #920 from mempool/simon/display-transaction-prevout-type
Display previous output types
2021-11-10 23:06:26 +09:00
wiz
61ffcd0065 Merge pull request #918 from mempool/simon/taproot-tag
Adding Taproot transaction tag
2021-11-10 22:58:02 +09:00
wiz
88e79c220f Merge pull request #902 from MiguelMedeiros/fix-nginx-docs
Doc: Fix nginx package name.
2021-11-10 22:33:34 +09:00
Miguel Medeiros
a2c21e9036 Add id to size string. 2021-11-10 10:26:26 -03:00
Miguel Medeiros
8a0316a562 Add localize strings at echarts tooltip. 2021-11-10 10:25:00 -03:00
softsimon
055c1f2aa5 Display previous output types
fixes #917
2021-11-10 16:17:03 +04:00
softsimon
88e48df8a9 Adding Taproot transaction tag
fixes #914
2021-11-10 15:05:45 +04:00
softsimon
b81d296635 Merge pull request #916 from mempool/wiz/enable-autocomplete-for-all-networks
Enable address autocomplete for all networks
2021-11-10 11:43:48 +04:00
wiz
9f68f57d8a Enable address autocomplete for all networks 2021-11-10 07:56:07 +01:00
softsimon
26a540a57c Display lower <1 s/vB fee rate tiers for Liquid
fixes #859
2021-11-10 00:04:58 +04:00
softsimon
ad8398e3d4 Merge pull request #897 from MiguelMedeiros/fix-rtl-menu-mobile
UI/UX: Fix rtl mobile header menu.
2021-11-06 01:50:51 +04:00
softsimon
0fabf8dbc3 Merge pull request #905 from MiguelMedeiros/add-2y-3y-graph-time-span
UI/UX: Add 2y and 3y statistics time span.
2021-11-05 23:09:35 +04:00
softsimon
cd63c6c0c1 Merge pull request #890 from MiguelMedeiros/fix-buttons-graph-page
UI/UX: Fix buttons positions at graphs page.
2021-11-04 13:21:00 +04:00
Miguel Medeiros
c95f75254b Add 2y and 3y statistics time span. 2021-11-01 22:06:10 -03:00
Miguel Medeiros
9e4ad51b79 Fix nginx docs. 2021-11-01 20:57:20 -03:00
Miguel Medeiros
6e35102b3a Fix rtl mobile header menu. 2021-11-01 11:11:50 -03:00
Miguel Medeiros
d1e72c0cc0 Fix loading icon position. 2021-10-29 10:46:16 -03:00
Miguel Medeiros
1925023eb2 fix: check buttons at graphs page 2021-10-29 10:04:30 -03:00
softsimon
377eb0cae5 Merge pull request #892 from MiguelMedeiros/fix-tooltip-echarts-liquid
UI/UX: Fix Liquid.network missing tooltip series name.
2021-10-29 13:28:21 +04:00
softsimon
e33e4b1d71 Merge pull request #873 from MiguelMedeiros/fix-height-title-components
UI/UX: Fix height inconsistencies between components.
2021-10-29 12:14:26 +04:00
softsimon
b8f3c1f124 Merge pull request #884 from mempool/simon/electrs-error-handling
Handle new type of Electrum Server error.
2021-10-29 11:29:49 +04:00
wiz
5333ec0b99 Merge pull request #893 from knorrium/e2e_base_module_cleanup
e2e BASE_MODULE cleanup
2021-10-28 14:30:06 +09:00
wiz
f761c5d966 Merge pull request #894 from knorrium/fe_dev_instructions 2021-10-28 14:13:58 +09:00
Felipe Knorr Kuhn
3b6d07cace Add instructions on how to contribute to the Frontend codebase 2021-10-27 21:50:51 -07:00
Felipe Knorr Kuhn
cbeeef3b2c Remove BASE_MODULE from the GHA env vars as we read from the config now 2021-10-27 20:52:50 -07:00
Felipe Knorr Kuhn
5dab44e6c4 Cleanup Cypress config and commands 2021-10-27 20:49:49 -07:00
Felipe Knorr Kuhn
5139ffb4df Update e2e tests BASE_MODULE resolution 2021-10-27 20:40:44 -07:00
Miguel Medeiros
06706be18f fix: Liquid missing tooltip serie name. 2021-10-27 01:58:54 -03:00
Miguel Medeiros
9264f3cf4f Fix title styles. 2021-10-26 21:47:28 -03:00
Miguel Medeiros
d6a9017267 Fix disable button font-size. 2021-10-26 21:47:27 -03:00
Miguel Medeiros
bad75cfd4e Fix address title styles.
Fix tx title margin right .
2021-10-26 21:47:27 -03:00
Miguel Medeiros
68240e4f5c Fix skeleton css styles. 2021-10-26 21:47:26 -03:00
Miguel Medeiros
9d9ff6ed91 Fix titles height inconsistencies. 2021-10-26 21:47:26 -03:00
softsimon
f19b84090a Merge pull request #887 from knorrium/fix_ws_api_docs
Fix ws api docs
2021-10-25 18:11:40 +04:00
Felipe Knorr Kuhn
85c4574cbd Update HTML template for the WS API examples 2021-10-24 22:47:50 -07:00
Felipe Knorr Kuhn
e2c4e42c81 Fix WS API examples 2021-10-24 22:47:02 -07:00
softsimon
dd6c26b079 Handle new type of Electrum Server error.
fixes #872
2021-10-23 11:46:38 +04:00
Felipe Knorr Kuhn
837992f7ea Route json assets based on the BASE_MODULE 2021-10-22 11:54:28 -07:00
Felipe Knorr Kuhn
40914236d4 Update references to the JS proxy config 2021-10-22 11:52:23 -07:00
Felipe Knorr Kuhn
f52c36093e Remove old json based proxy config 2021-10-22 11:51:52 -07:00
wiz
a9f4418e1a Merge pull request #880 from mempool/simon/transaction-output-id
Add output ID to transaction info
2021-10-20 22:32:31 +09:00
softsimon
1a37c8b116 Add output ID to transaction info
fixes #413
2021-10-20 15:44:43 +04:00
wiz
39d231bb3c Merge pull request #879 from mempool/simon/push-tx-form
Broadcast transaction form
2021-10-19 22:30:03 +09:00
softsimon
4046c3176f Broadcast transaction form
fixes #878
2021-10-19 17:10:30 +04:00
softsimon
c257fbfdcb Merge pull request #874 from MiguelMedeiros/fix-typo
Ref: Fix typo.
2021-10-18 16:26:07 +04:00
Miguel Medeiros
1659be0d9f Fix typo. 2021-10-12 10:54:14 -03:00
174 changed files with 37640 additions and 31751 deletions

View File

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

View File

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

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

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

View File

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

1
.nvmrc Normal file
View File

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

View File

@@ -1,8 +1,8 @@
# The Mempool Open Source Project
# The Mempool Open Source Project
Mempool is the fully featured visualizer, explorer, and API service running on [mempool.space](https://mempool.space/), an open source project developed and operated for the benefit of the Bitcoin community, with a focus on the emerging transaction fee market to help our transition into a multi-layer ecosystem.
![mempool](https://mempool.space/resources/screenshots/v2.2.1-dashboard.png)
![mempool](https://mempool.space/resources/screenshots/v2.3.0-dashboard.png)
## Installation Methods
@@ -20,11 +20,11 @@ The following instructions are for a manual installation on Linux or FreeBSD. Th
## Dependencies
* Bitcoin Core (no pruning, txindex=1)
* Electrum Server (romanz/electrs)
* NodeJS (official stable LTS)
* MariaDB (default config)
* Nginx (use supplied nginx.conf and nginx-mempool.conf)
* [Bitcoin](https://github.com/bitcoin/bitcoin)
* [Electrum](https://github.com/romanz/electrs)
* [NodeJS](https://github.com/nodejs/node)
* [MariaDB](https://github.com/mariadb/server)
* [Nginx](https://github.com/nginx/nginx)
## Mempool
@@ -41,7 +41,7 @@ Clone the mempool repo, and checkout the latest release tag:
Enable RPC and txindex in `bitcoin.conf`:
```bash
rpcuser=mempool
rpcpassword=71b61986da5b03a5694d7c7d5165ece5
rpcpassword=mempool
txindex=1
```
@@ -54,7 +54,7 @@ Install MariaDB from OS package manager:
# macOS
brew install mariadb
brew services start mariadb
mysql.server start
```
Create database and grant privileges:
@@ -80,7 +80,7 @@ Install mempool dependencies from npm and build the backend:
```bash
# backend
cd backend
npm install
npm install --prod
npm run build
```
@@ -96,18 +96,18 @@ Edit `mempool-config.json` to add your Bitcoin Core node RPC credentials:
"MEMPOOL": {
"NETWORK": "mainnet",
"BACKEND": "electrum",
"HTTP_PORT": 8999,
"API_URL_PREFIX": "/api/v1/",
"POLL_RATE_MS": 2000
"HTTP_PORT": 8999
},
"CORE_RPC": {
"HOST": "127.0.0.1",
"PORT": 8332,
"USERNAME": "mempool",
"PASSWORD": "71b61986da5b03a5694d7c7d5165ece5"
"PASSWORD": "mempool"
},
"ELECTRUM": {
"HOST": "127.0.0.1",
"PORT": 50002,
"TLS_ENABLED": true,
"TLS_ENABLED": true
},
"DATABASE": {
"ENABLED": true,
@@ -116,10 +116,6 @@ Edit `mempool-config.json` to add your Bitcoin Core node RPC credentials:
"USERNAME": "mempool",
"PASSWORD": "mempool",
"DATABASE": "mempool"
},
"STATISTICS": {
"ENABLED": true,
"TX_PER_SECOND_SAMPLE_PERIOD": 150
}
}
```
@@ -160,14 +156,14 @@ Install mempool dependencies from npm and build the frontend static HTML/CSS/JS:
```bash
# frontend
cd frontend
npm install
npm install --prod
npm run build
```
Install the output into nginx webroot folder:
```bash
sudo rsync -av --delete dist/mempool /var/www/
sudo rsync -av --delete dist/mempool/ /var/www/
```
## nginx + certbot
@@ -176,7 +172,7 @@ Install the supplied nginx.conf and nginx-mempool.conf in /etc/nginx
```bash
# install nginx and certbot
apt-get install -y nginx python-certbot-nginx
apt-get install -y nginx python3-certbot-nginx
# install the mempool configuration for nginx
cp nginx.conf nginx-mempool.conf /etc/nginx/

7
backend/.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,178 @@
import config from '../config';
import { DB } from '../database';
import logger from '../logger';
class DatabaseMigration {
private static currentVersion = 1;
private queryTimeout = 120000;
constructor() { }
public async $initializeOrMigrateDatabase(): Promise<void> {
if (!await this.$checkIfTableExists('statistics')) {
await this.$initializeDatabaseTables();
}
if (await this.$checkIfTableExists('state')) {
const databaseSchemaVersion = await this.$getSchemaVersionFromDatabase();
if (DatabaseMigration.currentVersion > databaseSchemaVersion) {
await this.$migrateTableSchemaFromVersion(databaseSchemaVersion);
}
} else {
await this.$migrateTableSchemaFromVersion(0);
}
}
private async $initializeDatabaseTables(): Promise<void> {
const connection = await DB.pool.getConnection();
for (const query of this.getInitializeTableQueries()) {
await connection.query<any>({ sql: query, timeout: this.queryTimeout });
}
connection.release();
logger.info(`Initial database tables have been created`);
}
private async $migrateTableSchemaFromVersion(version: number): Promise<void> {
const connection = await DB.pool.getConnection();
for (const query of this.getMigrationQueriesFromVersion(version)) {
await connection.query<any>({ sql: query, timeout: this.queryTimeout });
}
connection.release();
await this.$updateToLatestSchemaVersion();
logger.info(`Database schema have been migrated from version ${version} to ${DatabaseMigration.currentVersion} (latest version)`);
}
private async $getSchemaVersionFromDatabase(): Promise<number> {
const connection = await DB.pool.getConnection();
const query = `SELECT number FROM state WHERE name = 'schema_version';`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return rows[0]['number'];
}
private async $updateToLatestSchemaVersion(): Promise<void> {
const connection = await DB.pool.getConnection();
const query = `UPDATE state SET number = ${DatabaseMigration.currentVersion} WHERE name = 'schema_version'`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
}
private async $checkIfTableExists(table: string): Promise<boolean> {
const connection = await DB.pool.getConnection();
const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return rows[0]['COUNT(*)'] === 1;
}
private getInitializeTableQueries(): string[] {
const queries: string[] = [];
queries.push(`CREATE TABLE IF NOT EXISTS statistics (
id int(11) NOT NULL,
added datetime NOT NULL,
unconfirmed_transactions int(11) UNSIGNED NOT NULL,
tx_per_second float UNSIGNED NOT NULL,
vbytes_per_second int(10) UNSIGNED NOT NULL,
mempool_byte_weight int(10) UNSIGNED NOT NULL,
fee_data longtext NOT NULL,
total_fee double UNSIGNED NOT NULL,
vsize_1 int(11) NOT NULL,
vsize_2 int(11) NOT NULL,
vsize_3 int(11) NOT NULL,
vsize_4 int(11) NOT NULL,
vsize_5 int(11) NOT NULL,
vsize_6 int(11) NOT NULL,
vsize_8 int(11) NOT NULL,
vsize_10 int(11) NOT NULL,
vsize_12 int(11) NOT NULL,
vsize_15 int(11) NOT NULL,
vsize_20 int(11) NOT NULL,
vsize_30 int(11) NOT NULL,
vsize_40 int(11) NOT NULL,
vsize_50 int(11) NOT NULL,
vsize_60 int(11) NOT NULL,
vsize_70 int(11) NOT NULL,
vsize_80 int(11) NOT NULL,
vsize_90 int(11) NOT NULL,
vsize_100 int(11) NOT NULL,
vsize_125 int(11) NOT NULL,
vsize_150 int(11) NOT NULL,
vsize_175 int(11) NOT NULL,
vsize_200 int(11) NOT NULL,
vsize_250 int(11) NOT NULL,
vsize_300 int(11) NOT NULL,
vsize_350 int(11) NOT NULL,
vsize_400 int(11) NOT NULL,
vsize_500 int(11) NOT NULL,
vsize_600 int(11) NOT NULL,
vsize_700 int(11) NOT NULL,
vsize_800 int(11) NOT NULL,
vsize_900 int(11) NOT NULL,
vsize_1000 int(11) NOT NULL,
vsize_1200 int(11) NOT NULL,
vsize_1400 int(11) NOT NULL,
vsize_1600 int(11) NOT NULL,
vsize_1800 int(11) NOT NULL,
vsize_2000 int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
queries.push(`ALTER TABLE statistics ADD PRIMARY KEY (id);`);
queries.push(`ALTER TABLE statistics MODIFY id int(11) NOT NULL AUTO_INCREMENT;`);
return queries;
}
private getMigrationQueriesFromVersion(version: number): string[] {
const queries: string[] = [];
if (version < 1) {
if (config.MEMPOOL.NETWORK !== 'liquid') {
queries.push(`UPDATE statistics SET
vsize_1 = vsize_1 + vsize_2, vsize_2 = vsize_3,
vsize_3 = vsize_4, vsize_4 = vsize_5,
vsize_5 = vsize_6, vsize_6 = vsize_8,
vsize_8 = vsize_10, vsize_10 = vsize_12,
vsize_12 = vsize_15, vsize_15 = vsize_20,
vsize_20 = vsize_30, vsize_30 = vsize_40,
vsize_40 = vsize_50, vsize_50 = vsize_60,
vsize_60 = vsize_70, vsize_70 = vsize_80,
vsize_80 = vsize_90, vsize_90 = vsize_100,
vsize_100 = vsize_125, vsize_125 = vsize_150,
vsize_150 = vsize_175, vsize_175 = vsize_200,
vsize_200 = vsize_250, vsize_250 = vsize_300,
vsize_300 = vsize_350, vsize_350 = vsize_400,
vsize_400 = vsize_500, vsize_500 = vsize_600,
vsize_600 = vsize_700, vsize_700 = vsize_800,
vsize_800 = vsize_900, vsize_900 = vsize_1000,
vsize_1000 = vsize_1200, vsize_1200 = vsize_1400,
vsize_1400 = vsize_1800, vsize_1800 = vsize_2000, vsize_2000 = 0`);
}
queries.push(`CREATE TABLE IF NOT EXISTS elements_pegs (
block int(11) NOT NULL,
datetime int(11) NOT NULL,
amount bigint(20) NOT NULL,
txid varchar(65) NOT NULL,
txindex int(11) NOT NULL,
bitcoinaddress varchar(100) NOT NULL,
bitcointxid varchar(65) NOT NULL,
bitcoinindex int(11) NOT NULL,
final_tx int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
queries.push(`CREATE TABLE IF NOT EXISTS state (
name varchar(25) NOT NULL,
number int(11) NULL,
string varchar(100) NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
queries.push(`INSERT INTO state VALUES('schema_version', 0, NULL);`);
queries.push(`INSERT INTO state VALUES('last_elements_block', 0, NULL);`);
}
return queries;
}
}
export default new DatabaseMigration();

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ interface IConfig {
MEMPOOL_BLOCKS_AMOUNT: number;
PRICE_FEED_UPDATE_INTERVAL: number;
USE_SECOND_NODE_FOR_MINFEE: boolean;
EXTERNAL_ASSETS: string[];
};
ESPLORA: {
REST_API_URL: string;
@@ -78,6 +79,7 @@ const defaults: IConfig = {
'MEMPOOL_BLOCKS_AMOUNT': 8,
'PRICE_FEED_UPDATE_INTERVAL': 3600,
'USE_SECOND_NODE_FOR_MINFEE': false,
'EXTERNAL_ASSETS': [],
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',

View File

@@ -21,6 +21,9 @@ import backendInfo from './api/backend-info';
import loadingIndicators from './api/loading-indicators';
import mempool from './api/mempool';
import elementsParser from './api/liquid/elements-parser';
import databaseMigration from './api/database-migration';
import syncAssets from './sync-assets';
import icons from './api/liquid/icons';
class Server {
private wss: WebSocket.Server | undefined;
@@ -69,23 +72,34 @@ class Server {
next();
})
.use(express.urlencoded({ extended: true }))
.use(express.json());
.use(express.text())
;
this.server = http.createServer(this.app);
this.wss = new WebSocket.Server({ server: this.server });
this.setUpWebsocketHandling();
await syncAssets.syncAssets();
diskCache.loadMempoolCache();
if (config.DATABASE.ENABLED) {
await checkDbConnection();
try {
await databaseMigration.$initializeOrMigrateDatabase();
} catch (e) {
throw new Error(e instanceof Error ? e.message : 'Error');
}
}
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isMaster) {
statistics.startStatistics();
}
if (config.MEMPOOL.NETWORK === 'liquid') {
icons.loadIcons();
}
fiatConversion.startService();
this.setUpHttpApiRoutes();
@@ -142,7 +156,7 @@ class Server {
if (this.wss) {
websocketHandler.setWebsocketServer(this.wss);
}
if (config.MEMPOOL.NETWORK === 'liquid') {
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
blocks.setNewBlockCallback(async () => {
try {
await elementsParser.$parse();
@@ -169,6 +183,7 @@ class Server {
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', routes.getBackendInfo)
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', routes.getInitData)
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', routes.validateAddress)
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', routes.$postTransactionForm)
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
try {
const response = await axios.get('https://mempool.space/api/v1/donations', { responseType: 'stream', timeout: 10000 });
@@ -216,6 +231,8 @@ class Server {
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3m', routes.get3MStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/6m', routes.get6MStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.get1YStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.get2YStatistics.bind(routes))
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.get3YStatistics.bind(routes))
;
}
@@ -267,6 +284,13 @@ class Server {
}
if (config.MEMPOOL.NETWORK === 'liquid') {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
.get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
;
}
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
;

View File

@@ -19,6 +19,7 @@ import loadingIndicators from './api/loading-indicators';
import { Common } from './api/common';
import bitcoinClient from './api/bitcoin/bitcoin-client';
import elementsParser from './api/liquid/elements-parser';
import icons from './api/liquid/icons';
class Routes {
constructor() {}
@@ -52,6 +53,14 @@ class Routes {
res.json(statistics.getCache()['1y']);
}
public get2YStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['2y']);
}
public get3YStatistics(req: Request, res: Response) {
res.json(statistics.getCache()['3y']);
}
public getInitData(req: Request, res: Response) {
try {
const result = websocketHandler.getInitData();
@@ -613,7 +622,7 @@ class Routes {
const addressData = await bitcoinApi.$getAddress(req.params.address);
res.json(addressData);
} catch (e) {
if (e instanceof Error && e.message && e.message.indexOf('exceeds') > 0) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
return res.status(413).send(e instanceof Error ? e.message : e);
}
res.status(500).send(e instanceof Error ? e.message : e);
@@ -630,7 +639,7 @@ class Routes {
const transactions = await bitcoinApi.$getAddressTransactions(req.params.address, req.params.txId);
res.json(transactions);
} catch (e) {
if (e instanceof Error && e.message && e.message.indexOf('exceeds') > 0) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
return res.status(413).send(e instanceof Error ? e.message : e);
}
res.status(500).send(e instanceof Error ? e.message : e);
@@ -716,14 +725,16 @@ class Routes {
const nextRetargetHeight = blockHeight + remainingBlocks;
let difficultyChange = 0;
if (blocksInEpoch > 0) {
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (difficultyChange > 300) {
difficultyChange = 300;
}
if (difficultyChange < -75) {
difficultyChange = -75;
if (remainingBlocks < 1870) {
if (blocksInEpoch > 0) {
difficultyChange = (600 / (diff / blocksInEpoch ) - 1) * 100;
}
if (difficultyChange > 300) {
difficultyChange = 300;
}
if (difficultyChange < -75) {
difficultyChange = -75;
}
}
const timeAvgDiff = difficultyChange * 0.1;
@@ -768,14 +779,55 @@ class Routes {
public async $postTransaction(req: Request, res: Response) {
res.setHeader('content-type', 'text/plain');
try {
const rawtx = Object.keys(req.body)[0];
const txIdResult = await bitcoinApi.$sendRawTransaction(rawtx);
let rawTx;
if (typeof req.body === 'object') {
rawTx = Object.keys(req.body)[0];
} else {
rawTx = req.body;
}
const txIdResult = await bitcoinApi.$sendRawTransaction(rawTx);
res.send(txIdResult);
} catch (e: any) {
res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
}
}
public async $postTransactionForm(req: Request, res: Response) {
res.setHeader('content-type', 'text/plain');
const matches = /tx=([a-z0-9]+)/.exec(req.body);
let txHex = '';
if (matches && matches[1]) {
txHex = matches[1];
}
try {
const txIdResult = await bitcoinClient.sendRawTransaction(txHex);
res.send(txIdResult);
} catch (e: any) {
res.status(400).send(e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
}
}
public getLiquidIcon(req: Request, res: Response) {
const result = icons.getIconByAssetId(req.params.assetId);
if (result) {
res.setHeader('content-type', 'image/png');
res.setHeader('content-length', result.length);
res.send(result);
} else {
res.status(404).send('Asset icon not found');
}
}
public getAllLiquidIcon(req: Request, res: Response) {
const result = icons.getAllIconIds();
if (result) {
res.json(result);
} else {
res.status(404).send('Asset icons not found');
}
}
}
export default new Routes();

View File

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

View File

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

View File

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

View File

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

1
frontend/.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

15504
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -15,7 +15,6 @@ try {
throw new Error(e);
} else {
console.log(`${CONFIG_FILE_NAME} file not found, using default config`);
}
}
@@ -61,4 +60,20 @@ PROXY_CONFIG = [
}
];
if (configContent && configContent.BASE_MODULE == "liquid") {
PROXY_CONFIG.push({
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'],
target: "https://liquid.network",
secure: false,
changeOrigin: true,
});
} else {
PROXY_CONFIG.push({
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'],
target: "https://mempool.space",
secure: false,
changeOrigin: true,
});
}
module.exports = PROXY_CONFIG;

View File

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

View File

@@ -0,0 +1,73 @@
const fs = require('fs');
let PROXY_CONFIG = require('./proxy.conf.js');
const BACKEND_CONFIG_FILE_NAME = '../backend/mempool-config.json';
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
let backendConfigContent;
let frontendConfigContent;
// Read frontend config
try {
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
frontendConfigContent = JSON.parse(rawConfig);
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
} catch (e) {
console.log(e);
if (e.code !== 'ENOENT') {
throw new Error(e);
} else {
console.log(`${FRONTEND_CONFIG_FILE_NAME} file not found, using default config`);
}
}
// Read backend config
try {
const rawConfig = fs.readFileSync(BACKEND_CONFIG_FILE_NAME);
backendConfigContent = JSON.parse(rawConfig);
console.log(`${BACKEND_CONFIG_FILE_NAME} file found, using provided config`);
} catch (e) {
console.log(e);
if (e.code !== 'ENOENT') {
throw new Error(e);
} else {
console.log(`${BACKEND_CONFIG_FILE_NAME} file not found, using default config`);
}
}
// Remove the "/api/**" entry from the default proxy config
let localDevContext = PROXY_CONFIG[0].context
localDevContext.splice(PROXY_CONFIG[0].context.indexOf('/api/**'), 1);
PROXY_CONFIG[0].context = localDevContext;
// Change all targets to localhost
PROXY_CONFIG.map(conf => conf.target = "http://localhost:8999");
// Add rules for local backend
if (backendConfigContent) {
PROXY_CONFIG.push({
context: ['/api/address/**', '/api/tx/**', '/api/block/**', '/api/blocks/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
pathRewrite: {
"^/api/": "/api/v1/"
},
});
PROXY_CONFIG.push({
context: ['/api/v1/**'],
target: `http://localhost:8999`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000
});
}
console.log(PROXY_CONFIG);
module.exports = PROXY_CONFIG;

View File

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

View File

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

View File

@@ -14,19 +14,24 @@ import { AssetsComponent } from './assets/assets.component';
import { StatusViewComponent } from './components/status-view/status-view.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
import { ApiDocsComponent } from './components/api-docs/api-docs.component';
import { DocsComponent } from './components/docs/docs.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component';
import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component';
import { SponsorComponent } from './components/sponsor/sponsor.component';
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
let routes: Routes = [
{
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -61,9 +66,21 @@ let routes: Routes = [
path: 'about',
component: AboutComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
component: ApiDocsComponent,
redirectTo: 'docs/api/rest'
},
{
path: 'terms-of-service',
@@ -95,6 +112,10 @@ let routes: Routes = [
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -137,9 +158,21 @@ let routes: Routes = [
path: 'assets',
component: AssetsComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
component: ApiDocsComponent,
redirectTo: 'docs/api/rest'
},
],
},
@@ -164,6 +197,10 @@ let routes: Routes = [
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -199,9 +236,21 @@ let routes: Routes = [
children: [],
component: AddressComponent
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
component: ApiDocsComponent,
redirectTo: 'docs/api/rest'
},
],
},
@@ -226,6 +275,10 @@ let routes: Routes = [
path: '',
component: MasterPageComponent,
children: [
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: '',
component: StartComponent,
@@ -261,9 +314,21 @@ let routes: Routes = [
children: [],
component: AddressComponent
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
component: ApiDocsComponent,
redirectTo: 'docs/api/rest'
},
],
},
@@ -325,6 +390,10 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
path: '',
component: DashboardComponent
},
{
path: 'tx/push',
component: PushTransactionComponent,
},
{
path: 'tx/:id',
component: TransactionComponent
@@ -359,9 +428,21 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
path: 'assets',
component: AssetsComponent,
},
{
path: 'docs/api/:type',
component: DocsComponent
},
{
path: 'docs/api',
redirectTo: 'docs/api/rest'
},
{
path: 'docs',
redirectTo: 'docs/api/rest'
},
{
path: 'api',
component: ApiDocsComponent,
redirectTo: 'docs/api/rest'
},
{
path: 'about',
@@ -408,3 +489,4 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
<div class="container-xl">
<h1 style="float: left;" i18n="Registered assets page header">Registered assets</h1>
<br>
<div class="title-asset">
<h1 i18n="Registered assets page header">Registered assets</h1>
</div>
<div class="clearfix"></div>
<form [formGroup]="searchForm" class="form-inline">
<div class="input-group m-2">
<div class="input-group mb-2">
<input style="width: 250px;" formControlName="searchText" type="text" class="form-control" i18n-placeholder="Search Assets Placeholder Text" placeholder="Search asset">
<div class="input-group-append">
<button [disabled]="!searchForm.get('searchText')?.value.length" class="btn btn-secondary" type="button" (click)="searchForm.get('searchText')?.setValue('');" autocomplete="off" i18n="Search Clear Button">Clear</button>
@@ -27,7 +27,7 @@
<td>{{ asset.ticker }}</td>
<td class="d-none d-md-block">{{ asset.entity && asset.entity.domain }}</td>
<td><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
</tr>
</tr>
</tbody>
</table>
@@ -52,7 +52,7 @@
<td><span class="skeleton-loader"></span></td>
<td class="d-none d-md-block"><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tr>
</tbody>
</table>
@@ -68,4 +68,4 @@
</div>
<br>
<br>

View File

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

View File

@@ -1,9 +1,11 @@
<div class="container-xl">
<div class="title-block">
<h1><ng-template [ngIf]="blockHeight" i18n="block.block">Block <a [routerLink]="['/block/' | relativeUrl, blockHash]">{{ blockHeight }}</a></ng-template></h1>
<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">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ export class AddressComponent implements OnInit, OnDestroy {
isLoadingAddress = true;
transactions: Transaction[];
isLoadingTransactions = true;
retryLoadmore = false;
retryLoadMore = false;
error: any;
mainSubscription: Subscription;
addressLoadingStatus$: Observable<number>;
@@ -33,7 +33,7 @@ export class AddressComponent implements OnInit, OnDestroy {
totalConfirmedTxCount = 0;
loadedConfirmedTxCount = 0;
txCount = 0;
receieved = 0;
received = 0;
sent = 0;
private tempTransactions: Transaction[];
@@ -183,7 +183,7 @@ export class AddressComponent implements OnInit, OnDestroy {
});
transaction.vout.forEach((vout) => {
if (vout.scriptpubkey_address === this.address.address) {
this.receieved += vout.value;
this.received += vout.value;
}
});
});
@@ -206,7 +206,7 @@ export class AddressComponent implements OnInit, OnDestroy {
return;
}
this.isLoadingTransactions = true;
this.retryLoadmore = false;
this.retryLoadMore = false;
this.electrsApiService.getAddressTransactionsFromHash$(this.address.address, this.lastTransactionTxId)
.subscribe((transactions: Transaction[]) => {
this.lastTransactionTxId = transactions[transactions.length - 1].txid;
@@ -216,12 +216,12 @@ export class AddressComponent implements OnInit, OnDestroy {
},
(error) => {
this.isLoadingTransactions = false;
this.retryLoadmore = true;
this.retryLoadMore = true;
});
}
updateChainStats() {
this.receieved = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum;
this.received = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum;
this.sent = this.address.chain_stats.spent_txo_sum + this.address.mempool_stats.spent_txo_sum;
this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count;
this.totalConfirmedTxCount = this.address.chain_stats.tx_count;

View File

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

View File

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

View File

@@ -1,11 +1,14 @@
<div class="container-xl">
<h1 style="float: left;" i18n="asset|Liquid Asset page title">Asset</h1>
<a [routerLink]="['/asset/' | relativeUrl, assetString]" style="line-height: 56px; margin-left: 10px;">
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ assetString }}</span>
</a>
<app-clipboard [text]="assetString"></app-clipboard>
<br>
<div class="title-asset">
<h1 i18n="asset|Liquid Asset page title">Asset</h1>
<div class="tx-link">
<a [routerLink]="['/asset/' | relativeUrl, assetString]">
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ assetString }}</span>
</a>
<app-clipboard [text]="assetString"></app-clipboard>
</div>
</div>
<div class="clearfix"></div>
@@ -72,12 +75,14 @@
<br>
<h2>
<ng-template [ngIf]="transactions?.length" i18n="asset.M_of_N">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }}&nbsp;</ng-template>
<ng-template [ngIf]="isNativeAsset" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
<ng-template #defaultAsset i18n="Default asset transactions title">Issuance and Burn Transactions</ng-template>
</h2>
<div class="title-tx">
<h2>
<ng-template [ngIf]="transactions?.length" i18n="asset.M_of_N">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }}&nbsp;</ng-template>
<ng-template [ngIf]="isNativeAsset" [ngIfElse]="defaultAsset" i18n="Liquid native asset transactions title">Peg In/Out and Burn Transactions</ng-template>
<ng-template #defaultAsset i18n="Default asset transactions title">Issuance and Burn Transactions</ng-template>
</h2>
</div>
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
<div class="text-center">

View File

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

View File

@@ -38,8 +38,8 @@
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/stats']" (click)="collapse()"><fa-icon [icon]="['fas', 'file-alt']" [fixedWidth]="true" i18n-title="master-page.stats" title="Stats"></fa-icon></a>
</li>
<li class="nav-item mr-2" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/api' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cogs']" [fixedWidth]="true" i18n-title="master-page.api" title="API"></fa-icon></a>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/docs']" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>

View File

@@ -2,8 +2,8 @@
<div class="title-block" id="block">
<h1>
<ng-template [ngIf]="blockHeight === 0" i18n="block.genesis">Genesis
<div class="next-previous-blocks">
<ng-template [ngIf]="blockHeight === 0"><ng-container i18n="@@2303359202781425764">Genesis</ng-container>
<span class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
</a>
@@ -11,10 +11,11 @@
<span placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
</span>
</div>
</span>
</ng-template>
<ng-template [ngIf]="blockHeight" i18n="block.block"> Block
<div class="next-previous-blocks">
<ng-template [ngIf]="blockHeight" i18n="shared.block-title">Block <ng-container *ngTemplateOutlet="blockTemplateContent"></ng-container></ng-template>
<ng-template #blockTemplateContent>
<span class="next-previous-blocks">
<a *ngIf="showNextBlocklink" [routerLink]="['/block/' | relativeUrl, nextBlockHeight]" (click)="navigateToNextBlock()" i18n-ngbTooltip="Next Block" ngbTooltip="Next Block" placement="bottom">
<fa-icon [icon]="['fas', 'angle-left']" [fixedWidth]="true"></fa-icon>
</a>
@@ -28,7 +29,7 @@
<span *ngIf="!showPreviousBlocklink" placement="bottom" class="disable">
<fa-icon [icon]="['fas', 'angle-right']" [fixedWidth]="true"></fa-icon>
</span>
</div>
</span>
</ng-template>
</h1>
@@ -162,7 +163,7 @@
</div>
<div #blockTxTitle id="block-tx-title" class="block-tx-title">
<h2 class="float-left">
<h2>
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
@@ -175,14 +176,14 @@
<app-transactions-list [transactions]="transactions"></app-transactions-list>
<ng-template [ngIf]="isLoadingTransactions">
<div class="text-center mb-4 mt-3">
<div class="text-center mb-4" class="tx-skeleton">
<div class="header-bg box" style="padding: 10px; margin-bottom: 10px;">
<div class="header-bg box">
<span class="skeleton-loader"></span>
</div>
<div class="header-bg box">
<div class="row" style="height: 107px;">
<div class="row">
<div class="col-sm">
<span class="skeleton-loader"></span>
</div>
@@ -199,7 +200,7 @@
<div class="progress-bar progress-darklight" role="progressbar" [ngStyle]="{'width': txsLoadingStatus + '%' }"></div>
</div>
</ng-container>
</div>
</ng-template>
<ngb-pagination class="pagination-container float-right" [collectionSize]="block.tx_count" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page, blockTxTitle)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
@@ -207,7 +208,7 @@
</ng-template>
<ng-template [ngIf]="isLoadingBlock && !error">
<div class="box">
<div class="row">
<div class="col-sm">

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { Block } from 'src/app/interfaces/electrs.interface';
import { StateService } from 'src/app/services/state.service';
import { Router } from '@angular/router';
import { specialBlocks } from 'src/app/app.constants';
@Component({
selector: 'app-blockchain-blocks',
@@ -11,7 +12,7 @@ import { Router } from '@angular/router';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
specialBlocks = specialBlocks;
network = '';
blocks: Block[] = [];
emptyBlocks: Block[] = this.mountEmptyBlocks();
@@ -40,7 +41,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
};
constructor(
private stateService: StateService,
public stateService: StateService,
private router: Router,
private cd: ChangeDetectorRef,
) { }

View File

@@ -18,6 +18,11 @@
.blockchain-wrapper {
overflow: hidden;
height: 250px;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
}
.position-container {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
import { Env, StateService } from 'src/app/services/state.service';
import { Observable, merge, of } from 'rxjs';
import { SeoService } from 'src/app/services/seo.service';
@@ -16,12 +16,27 @@ export class ApiDocsComponent implements OnInit {
env: Env;
code: any;
baseNetworkUrl = '';
@Input() restTabActivated: Boolean;
@ViewChild( "mobileFixedApiNav", { static: false } ) mobileFixedApiNav: ElementRef;
desktopDocsNavPosition = "relative";
showFloatingDocsNav = false;
mobileMenuOpen = true;
constructor(
private stateService: StateService,
private seoService: SeoService,
) { }
ngAfterViewInit() {
const that = this;
setTimeout( () => {
window.addEventListener('scroll', function() {
that.desktopDocsNavPosition = ( window.pageYOffset > 182 ) ? "fixed" : "relative";
that.showFloatingDocsNav = ( window.pageYOffset > ( that.mobileFixedApiNav.nativeElement.offsetHeight + 188 ) ) ? true : false;
});
}, 1 );
}
ngOnInit(): void {
this.env = this.stateService.env;
this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`);
@@ -627,24 +642,6 @@ export class ApiDocsComponent implements OnInit {
console.log(asset);
`,
},
codeSampleMainnet: {
esModule: [],
commonJS: [],
curl: [],
response: ''
},
codeSampleTestnet: {
esModule: [],
commonJS: [],
curl: [],
response: ''
},
codeSampleSignet: {
esModule: [],
commonJS: [],
curl: [],
response: ''
},
codeSampleLiquid: {
esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
@@ -678,6 +675,47 @@ export class ApiDocsComponent implements OnInit {
response: ''
},
},
assetIcons: {
codeTemplate: {
curl: `/api/v1/assets/icons`,
commonJS: `
const { %{0}: { assets } } = mempoolJS();
const assetsIcons = await assets.getAssetsIcons();
document.getElementById("result").textContent = JSON.stringify(assetsIcons, undefined, 2);
`,
esModule: `
const { %{0}: { assets } } = mempoolJS();
const assetsIcons = await assets.getAssetsIcons();
console.log(assetsIcons);
`,
},
codeSampleLiquid: {
esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
curl: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
response: `[
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d",
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"
...
]`,
},
},
assetIcon: {
noWrap: true,
codeTemplate: {
curl: `/api/v1/asset/%{1}/icon`,
commonJS: `<img src="https://liquid.place/api/v1/asset/%{1}/icon">`,
},
codeSampleLiquid: {
esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
curl: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
response: `PNG`,
},
},
assetTransactions: {
codeTemplate: {
curl: `/api/asset/%{1}/txs`,
@@ -3416,8 +3454,8 @@ export class ApiDocsComponent implements OnInit {
ws.addEventListener('message', function incoming({data}) {
const res = JSON.parse(data.toString());
if (res.blocks) {
document.getElementById("result-blocks").textContent = JSON.stringify(res.blocks, undefined, 2);
if (res.block) {
document.getElementById("result-blocks").textContent = JSON.stringify(res.block, undefined, 2);
}
if (res.mempoolInfo) {
document.getElementById("result-mempool-info").textContent = JSON.stringify(res.mempoolInfo, undefined, 2);
@@ -3425,8 +3463,8 @@ export class ApiDocsComponent implements OnInit {
if (res.transactions) {
document.getElementById("result-transactions").textContent = JSON.stringify(res.transactions, undefined, 2);
}
if (res.mempoolBlocks) {
document.getElementById("result-mempool-blocks").textContent = JSON.stringify(res.mempoolBlocks, undefined, 2);
if (res["mempool-blocks"]) {
document.getElementById("result-mempool-blocks").textContent = JSON.stringify(res["mempool-blocks"], undefined, 2);
}
});
`,
@@ -3439,8 +3477,8 @@ export class ApiDocsComponent implements OnInit {
ws.on("message", function incoming(data) {
const res = JSON.parse(data.toString());
if (res.blocks) {
console.log(res.blocks);
if (res.block) {
console.log(res.block);
}
if (res.mempoolInfo) {
console.log(res.mempoolInfo);
@@ -3448,8 +3486,8 @@ export class ApiDocsComponent implements OnInit {
if (res.transactions) {
console.log(res.transactions);
}
if (res.mempoolBlocks) {
console.log(res.mempoolBlocks);
if (res["mempool-blocks"]) {
console.log(res["mempool-blocks"]);
}
});
`,

View File

@@ -164,6 +164,10 @@ init();`;
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleBisq.esModule);
}
if (code.noWrap) {
return codeText;
}
let importText = `<script src="https://mempool.space/mempool.js"></script>`;
if (this.env.BASE_MODULE === 'bisq') {
importText = `<script src="https://bisq.markets/bisq.js"></script>`;
@@ -174,10 +178,10 @@ init();`;
let resultHtml = '<pre id="result"></pre>';
if (this.method === 'websocket') {
resultHtml = `<pre id="result-blocks"></pre>
<pre id="result-mempool-info"></pre>
<pre id="result-transactions"></pre>
<pre id="result-mempool-blocks"></pre>`;
resultHtml = `<h2>Blocks</h2><pre id="result-blocks">Waiting for data</pre><br>
<h2>Mempool Info</h2><pre id="result-mempool-info">Waiting for data</pre><br>
<h2>Transactions</h2><pre id="result-transactions">Waiting for data</pre><br>
<h2>Mempool Blocks</h2><pre id="result-mempool-blocks">Waiting for data</pre><br>`;
}
return `<!DOCTYPE html>

View File

@@ -0,0 +1,39 @@
<div class="container-xl">
<div class="text-center">
<h2 i18n="documentation.title">Documentation</h2>
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs">
<li [ngbNavItem]="0">
<a ngbNavLink routerLink="../rest">API - REST</a>
<ng-template ngbNavContent>
<app-api-docs [restTabActivated]="true"></app-api-docs>
</ng-template>
</li>
<li [ngbNavItem]="1" *ngIf="showWebSocketTab">
<a ngbNavLink routerLink="../websocket">API - WebSocket</a>
<ng-template ngbNavContent>
<app-api-docs [restTabActivated]="false"></app-api-docs>
</ng-template>
</li>
</ul>
<div id="main-tab-content" [ngbNavOutlet]="nav" class="mt-2"></div>
<br>
<div id="footer" class="text-center">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,9 @@
#main-tab-content {
text-align: left;
padding-top: 10px;
scroll-behavior: smooth;
}
#footer {
clear: both;
}

View File

@@ -0,0 +1,27 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Env, StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-docs',
templateUrl: './docs.component.html',
styleUrls: ['./docs.component.scss']
})
export class DocsComponent implements OnInit {
activeTab = 0;
env: Env;
showWebSocketTab = true;
constructor(
private route: ActivatedRoute,
private stateService: StateService,
) { }
ngOnInit(): void {
const url = this.route.snapshot.url;
this.activeTab = ( url[2].path === "rest" ) ? 0 : 1;
this.env = this.stateService.env;
this.showWebSocketTab = ( ! ( ( this.env.BASE_MODULE === "bisq" ) || ( this.stateService.network === "bisq" ) ) );
}
}

View File

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

View File

@@ -1,12 +1,20 @@
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy } from '@angular/core';
import { formatDate } from '@angular/common';
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { EChartsOption } from 'echarts';
import { OnChanges } from '@angular/core';
import { StorageService } from 'src/app/services/storage.service';
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
@Component({
selector: 'app-incoming-transactions-graph',
templateUrl: './incoming-transactions-graph.component.html',
styles: [`
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 16px);
z-index: 100;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
@@ -17,7 +25,9 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
@Input() top: number | string = '20';
@Input() left: number | string = '0';
@Input() template: ('widget' | 'advanced') = 'widget';
@Input() windowPreferenceOverride: string;
isLoading = true;
mempoolStatsChartOption: EChartsOption = {};
mempoolStatsChartInitOption = {
renderer: 'svg'
@@ -29,13 +39,23 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
private storageService: StorageService,
) { }
ngOnInit() {
this.isLoading = true;
}
ngOnChanges(): void {
this.windowPreference = this.storageService.getValue('graphWindowPreference');
if (!this.data) {
return;
}
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
this.mountChart();
}
ngOnInit(): void {
this.mountChart();
rendered() {
if (!this.data) {
return;
}
this.isLoading = false;
}
mountChart(): void {
@@ -54,10 +74,12 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
maxSpan: 100,
minSpan: 10,
}, {
showDetail: false,
show: (this.template === 'advanced') ? true : false,
type: 'slider',
brushSelect: false,
realtime: true,
bottom: 0,
selectedDataBackground: {
lineStyle: {
color: '#fff',
@@ -66,7 +88,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
areaStyle: {
opacity: 0,
}
}
},
}],
tooltip: {
trigger: 'axis',
@@ -83,29 +105,39 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
type: 'line',
},
formatter: (params: any) => {
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
params.map((item: any, index: number) => {
if (index < 26) {
itemFormatted += `<div class="item">
<div class="indicator-container">${colorSpan(item.color)}</div>
<div class="grow"></div>
<div class="value">${item.value} <span class="symbol">vB/s</span></div>
<div class="value">${item.value[1]} <span class="symbol">vB/s</span></div>
</div>`;
}
});
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
}
},
xAxis: {
type: 'category',
axisLabel: {
align: 'center',
fontSize: 11,
lineHeight: 12
},
data: this.data.labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
},
xAxis: [
{
name: formatterXAxisLabel(this.locale, this.windowPreference),
nameLocation: 'middle',
nameTextStyle: {
padding: [20, 0, 0, 0],
},
type: 'time',
axisLabel: {
margin: 20,
align: 'center',
fontSize: 11,
lineHeight: 12,
hideOverlap: true,
padding: [0, 5],
},
}
],
yAxis: {
type: 'value',
axisLabel: {

View File

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

View File

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

View File

@@ -44,7 +44,7 @@
<a class="nav-link" [routerLink]="['/assets']" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
</li>
<li [hidden]="isMobile" class="nav-item mr-2" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/api' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cogs']" [fixedWidth]="true" i18n-title="master-page.api" title="API"></fa-icon></a>
<a class="nav-link" [routerLink]="['/docs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>
@@ -59,4 +59,4 @@
<router-outlet></router-outlet>
<br>
<br>

View File

@@ -57,8 +57,8 @@
<li *ngIf="network.val === 'liquid'" class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/liquid/assets']" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
</li>
<li [hidden]="isMobile" class="nav-item mr-2" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/api' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'cogs']" [fixedWidth]="true" i18n-title="master-page.api" title="API"></fa-icon></a>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/docs' | relativeUrl ]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="documentation.title" title="Documentation"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" [routerLink]="['/about']" (click)="collapse()"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" i18n-title="master-page.about" title="About"></fa-icon></a>

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
<ng-container *ngIf="(loadingBlocks$ | async) === false; else loadingBlocks">
<div class="mempool-blocks-container" *ngIf="(timeAvg$ | async) as timeAvg;">
<div class="flashing">
<div class="flashing">
<ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn">
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]">
<a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink">&nbsp;</a>
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
<a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div class="block-body">
<div class="fees">
~{{ projectedBlock.medianFee | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,24 @@
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
import { formatDate } from '@angular/common';
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
import { formatNumber } from "@angular/common";
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
import { StateService } from 'src/app/services/state.service';
import { StorageService } from 'src/app/services/storage.service';
import { EChartsOption } from 'echarts';
import { feeLevels, chartColors } from 'src/app/app.constants';
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
@Component({
selector: 'app-mempool-graph',
templateUrl: './mempool-graph.component.html',
styles: [`
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 16px);
z-index: 100;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MempoolGraphComponent implements OnInit, OnChanges {
@@ -24,7 +31,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
@Input() left: number | string = 75;
@Input() template: ('widget' | 'advanced') = 'widget';
@Input() showZoom = true;
@Input() windowPreferenceOverride: string;
isLoading = true;
mempoolVsizeFeesData: any;
mempoolVsizeFeesOptions: EChartsOption;
mempoolVsizeFeesInitOptions = {
@@ -45,16 +54,26 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
) { }
ngOnInit(): void {
this.isLoading = true;
this.inverted = this.storageService.getValue('inverted-graph') === 'true';
this.mountFeeChart();
}
ngOnChanges() {
this.windowPreference = this.storageService.getValue('graphWindowPreference');
if (!this.data) {
return;
}
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
this.mountFeeChart();
}
rendered() {
if (!this.data) {
return;
}
this.isLoading = false;
}
onChartReady(myChart: any) {
myChart.getZr().on('mousemove', (e: any) => {
if (e.target !== undefined &&
@@ -71,11 +90,6 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
const labels = mempoolStats.map(stats => stats.added);
const finalArrayVByte = this.generateArray(mempoolStats);
// Only Liquid has lower than 1 sat/vb transactions
if (this.stateService.network !== 'liquid') {
finalArrayVByte.shift();
}
return {
labels: labels,
series: finalArrayVByte
@@ -83,13 +97,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
}
generateArray(mempoolStats: OptimizedMempoolStats[]) {
const finalArray: number[][] = [];
let feesArray: number[] = [];
const limitFeesTemplate = this.template === 'advanced' ? 28 : 21;
const finalArray: number[][][] = [];
let feesArray: number[][] = [];
let limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
for (let index = limitFeesTemplate; index > -1; index--) {
feesArray = [];
mempoolStats.forEach((stats) => {
feesArray.push(stats.vsizes[index] ? stats.vsizes[index] : 0);
feesArray.push([stats.added * 1000, stats.vsizes[index] ? stats.vsizes[index] : 0]);
});
finalArray.push(feesArray);
}
@@ -99,7 +113,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
mountFeeChart() {
this.orderLevels();
const { labels, series } = this.mempoolVsizeFeesData;
const { series } = this.mempoolVsizeFeesData;
const seriesGraph = [];
const newColors = [];
@@ -172,14 +186,15 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
type: 'line',
},
formatter: (params: any) => {
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
const { totalValue, totalValueArray } = this.getTotalValues(params);
const itemFormatted = [];
let totalParcial = 0;
let progressPercentageText = '';
const items = this.inverted ? [...params].reverse() : params;
items.map((item: any, index: number) => {
totalParcial += item.value;
const progressPercentage = (item.value / totalValue) * 100;
totalParcial += item.value[1];
const progressPercentage = (item.value[1] / totalValue) * 100;
const progressPercentageSum = (totalValueArray[index] / totalValue) * 100;
let activeItemClass = '';
let hoverActive = 0;
@@ -219,7 +234,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
</td>
<td class="total-progress-sum">
<span>
${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)}
${this.vbytesPipe.transform(item.value[1], 2, 'vB', 'MvB', false)}
</span>
</td>
<td class="total-progress-sum">
@@ -238,9 +253,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
</tr>`);
});
const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : '';
const titleRange = $localize`Range`;
const titleSize = $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`;
const titleSum = $localize`Sum`;
return `<div class="fees-wrapper-tooltip-chart ${classActive}">
<div class="title">
${params[0].axisValue}
${axisValueLabel}
<span class="total-value">
${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
</span>
@@ -248,9 +266,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
<table>
<thead>
<tr>
<th>Range</th>
<th>Size</th>
<th>Sum</th>
<th>${titleRange}</th>
<th>${titleSize}</th>
<th>${titleSum}</th>
<th></th>
</tr>
</thead>
@@ -271,6 +289,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
maxSpan: 100,
minSpan: 10,
}, {
showDetail: false,
show: (this.template === 'advanced' && this.showZoom) ? true : false,
type: 'slider',
brushSelect: false,
@@ -295,15 +314,22 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
},
xAxis: [
{
type: 'category',
name: formatterXAxisLabel(this.locale, this.windowPreference),
nameLocation: 'middle',
nameTextStyle: {
padding: [20, 0, 0, 0],
},
type: 'time',
boundaryGap: false,
axisLine: { onZero: true },
axisLabel: {
margin: 20,
align: 'center',
fontSize: 11,
lineHeight: 12,
hideOverlap: true,
padding: [0, 5],
},
data: labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
}
],
yAxis: {
@@ -329,7 +355,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
const totalValueArray = [];
const valuesInverted = this.inverted ? values : [...values].reverse();
for (const item of valuesInverted) {
totalValueTemp += item.value;
totalValueTemp += item.value[1];
totalValueArray.push(totalValueTemp);
}
return {
@@ -345,10 +371,10 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
this.feeLimitIndex = i;
}
if (feeLevels[i] <= this.limitFee) {
if (i === 0) {
this.feeLevelsOrdered.push('0 - 1');
if (this.stateService.network === 'liquid') {
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
} else {
this.feeLevelsOrdered.push(`${feeLevels[i - 1]} - ${feeLevels[i]}`);
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
}
}
}

View File

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

View File

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

View File

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

View File

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

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