Merge branch 'simon/cpfp-frontend' into simon/bisq-dashboard

* simon/cpfp-frontend: (46 commits)
  Bugfix: Don't extend already extended transactions to not override the firstSeen property. fixes #390
  Shuffle mempool transactions before saving disk cache. (#398)
  Adding missing return after expressjs response.
  CPFP support (#395)
  Round sat/vB in fee rating tooltip. fixes #364
  Add the GNU AGPLv3 logo to About page
  Update package.json license tags
  Add recommended fee percentile config (#394)
  Fix typo in README (#392)
  Fix icon for Specter Wallet on About page
  Add link to Specter Wallet on our About page
  Add link to WARden Portfolio app as Community Integration on About page
  Delete MIT+CC license from Terms of Service, add AGPLv3 to About page
  Change mempool project license to GNU Affero General Public License v3
  Lower volume for sound effects (#385)
  Improve grammar, layout, and formatting of Terms of Service page
  Display all Project Contributors on About page using GitHub API (#382)
  Modify nginx.conf to cache HTML for 10m and static resources for 1h
  Proxy /api/v1/contributors from mempool.space, also fix HTTP headers
  Add link to Bisq's GitHub repo on About page
  ...
This commit is contained in:
softsimon
2021-03-21 06:12:41 +07:00
92 changed files with 3034 additions and 1949 deletions

View File

@@ -10,40 +10,42 @@
<br>
<h5 i18n="about.about-the-project">The Mempool Open Source Project</h5>
<div class="row row-cols-1">
<div class="col col-md-6 mx-auto">
<p i18n>mempool.space is developed and operated for the Bitcoin community, focusing on the emerging transaction fee market to help our transition into a multi-layer ecosystem, without ads, altcoins, or third-party trackers.</p>
<p i18n>An explorer and API developed and operated for the Bitcoin community, focusing on the emerging transaction fee market to help our transition into a multi-layer ecosystem, without ads, altcoins, or third-party trackers.</p>
</div>
</div>
<br>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://github.com/mempool/mempool">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16 fa-3x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
</span>
</a>
<h3 i18n="about.maintainers">Maintainers</h3>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://twitter.com/mempool">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-3x" 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>
</span>
</a>
<div class="container text-center">
<div class="row row-cols-2" dir="ltr">
<div class="col col-md-2 offset-md-4">
<a href="https://twitter.com/softsimon_">
<div class="profile_photo mx-auto" style="background-image: url(/resources/profile_softsimon.jpg)"></div>
@softsimon_
</a>
<br>
<span i18n="about.development">Development</span>
</div>
<div class="col col-md-2">
<a href="https://twitter.com/wiz">
<div class="profile_photo mx-auto" style="background-image: url(/resources/profile_wiz.png)"></div>
@wiz
</a>
<br>
<span i18n="about.operations">Operations</span>
</div>
</div>
</div>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://t.me/mempoolspace">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-3x" 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>
</span>
</a>
<br><br>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://keybase.io/team/mempool">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-3x" 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>
</span>
</a>
<h3 i18n="about.sponsors.withHeart">Sponsors ❤️</h3>
<br><br><br>
<h3 style="display: none;" i18n="about.sponsors.enterprise">Enterprise Sponsors</h3>
<h3 i18n="about.sponsors.withHeart">Community Sponsors ❤️</h3>
<div *ngIf="sponsors === null">
<br>
@@ -176,35 +178,180 @@
<br><br><br>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://github.com/mempool/mempool">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16 fa-3x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
</span>
<h3 i18n="about.integrations">Community Integrations</h3>
<a href="https://github.com/bisq-network/bisq" target="_blank">
<div class="profile_photo d-inline-block" title="Bisq">
<img class="profile_img" src="/resources/profile/bisq_network.png" />
Bisq
</div>
</a>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://twitter.com/mempoolspace">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-3x" 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>
</span>
<a href="https://github.com/getumbrel/umbrel" target="_blank">
<div class="profile_photo d-inline-block" title="Umbrel">
<img class="profile_img" src="/resources/profile/umbrel.png" />
Umbrel
</div>
</a>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://t.me/mempoolspace">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-3x" 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>
</span>
<a href="https://github.com/rootzoll/raspiblitz" target="_blank">
<div class="profile_photo d-inline-block" title="RaspiBlitz">
<img class="profile_img" src="/resources/profile/raspiblitz.jpg" />
RaspiBlitz
</div>
</a>
<a target="_blank" class="m-2 fw6 mb3 mt2 truncate black-80 f4 link" href="https://keybase.io/team/mempool">
<span class="dib v-mid">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-3x" 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>
</span>
<a href="https://github.com/mynodebtc/mynode" target="_blank">
<div class="profile_photo d-inline-block" title="MyNode">
<img class="profile_img" src="/resources/profile/mynodebtc.jpg" />
MyNode
</div>
</a>
<a href="https://github.com/RoninDojo/RoninDojo" target="_blank">
<div class="profile_photo d-inline-block" title="RoninDojo">
<img class="profile_img" src="/resources/profile/ronindojo.png" />
RoninDojo
</div>
</a>
<a href="https://github.com/ACINQ/phoenix" target="_blank">
<div class="profile_photo d-inline-block" title="Phoenix Wallet by ACINQ">
<img class="profile_img" src="/resources/profile/phoenix.jpg" />
Phoenix
</div>
</a>
<a href="https://github.com/cryptoadvance/specter-desktop" target="_blank">
<div class="profile_photo d-inline-block" title="Specter Wallet">
<img class="profile_img" src="/resources/profile/specter.png" />
Specter
</div>
</a>
<a href="https://github.com/spesmilo/electrum" target="_blank">
<div class="profile_photo d-inline-block" title="Electrum Wallet">
<img class="profile_img" src="/resources/profile/electrum.jpg" />
Electrum
</div>
</a>
<a href="https://github.com/hsjoberg/blixt-wallet" target="_blank">
<div class="profile_photo d-inline-block" title="Blixt Wallet">
<img class="profile_img" src="/resources/profile/blixt.png" />
Blixt
</div>
</a>
<a href="https://github.com/Satpile/satpile" target="_blank">
<div class="profile_photo d-inline-block" title="Satpile Watch-Only Wallet">
<img class="profile_img" src="/resources/profile/satpile.jpg" />
Satpile
</div>
</a>
<a href="https://github.com/btcontract/lnwallet" target="_blank">
<div class="profile_photo d-inline-block" title="Bitcoin Lightning Wallet">
<img class="profile_img" src="/resources/profile/blw.png" />
BLW
</div>
</a>
<a href="https://github.com/sparrowwallet/sparrow" target="_blank">
<div class="profile_photo d-inline-block" title="Sparrow Wallet">
<img class="profile_img" src="/resources/profile/sparrow.png" />
Sparrow
</div>
</a>
<a href="https://github.com/pxsocs/warden" target="_blank">
<div class="profile_photo d-inline-block" title="WARden Portfolio">
<img class="profile_img" src="/resources/profile/warden.jpg" />
WARden
</div>
</a>
<br><br><br>
<h3 i18n="about.alliances">Community Alliances</h3>
<a href="https://liquid.net/">
<img src="/resources/profile/liquid.svg" height="52">
</a>
<a href="https://opencrypto.org/">
<img src="/resources/profile/copa.png" height="62" style="margin-left: 30px; margin-right: 30px; margin-top: 20px;">
</a>
<a href="https://bisq.network/">
<img src="/resources/profile/bisq.svg" height="62">
</a>
<br><br><br>
<h3 i18n="about.contributors">Project Contributors</h3>
<div *ngIf="contributors === null">
<br>
<div class="spinner-border text-light"></div>
</div>
<ng-template ngFor let-contributor [ngForOf]="contributors">
<a [href]="'https://github.com/' + contributor.name" target="_blank">
<div class="profile_photo d-inline-block" [title]="contributor.name">
<img class="profile_img" [src]="'/api/v1/contributors/images/' + contributor.id" />
{{ contributor.name }}
</div>
</a>
</ng-template>
<br><br><br>
<h3 i18n="about.maintainers">Project Maintainers</h3>
<div class="container text-center">
<div class="row row-cols-2" dir="ltr">
<div class="col col-md-2 offset-md-4">
<a href="https://twitter.com/softsimon_">
<div class="profile_photo d-inline-block" title="softsimon">
<img class="profile_img" src="resources/profile/softsimon.jpg" />
softsimon
</div>
</a>
</div>
<div class="col col-md-2">
<a href="https://twitter.com/wiz">
<div class="profile_photo d-inline-block" title="wiz">
<img class="profile_img" src="resources/profile/wiz.png" />
wiz
</div>
</a>
</div>
</div>
</div>
<br>
<code>Copyright (c) 2019-2021</code><br>
<code>The Mempool Open Source Project</code><br>
<br>
<code>This program is free software: you can redistribute it and/or modify it</code><br>
<code>under the terms of the GNU Affero General Public License as published by</code><br>
<code>the Free Software Foundation, either version 3 of the License, or (at your</code><br>
<code>option) any later version.</code><br>
<br>
<code>This program is distributed in the hope that it will be useful, but</code><br>
<code>WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY</code><br>
<code>or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public</code><br>
<code>License for more details.</code><br>
<img src="/resources/profile/gnuagplv3.svg" style="margin: 20px; width: 200px">
<br>
<a href="https://www.gnu.org/licenses/agpl-3.0-standalone.html">GNU Affero General Public License v3.0</a>
<br><br>
<a href="/3rdpartylicenses.txt">Third Party Licenses</a>
</div>
<br><br>
<br>
<div class="text-center">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>

View File

@@ -19,9 +19,11 @@ export class AboutComponent implements OnInit, OnDestroy {
paymentForm: FormGroup;
donationStatus = 1;
sponsors$: Observable<any>;
contributors$: Observable<any>;
donationObj: any;
sponsorsEnabled = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
sponsors = null;
contributors = null;
requestSubscription: Subscription | undefined;
constructor(
@@ -51,6 +53,11 @@ export class AboutComponent implements OnInit, OnDestroy {
.subscribe((sponsors) => {
this.sponsors = sponsors;
});
this.apiService.getContributor$()
.subscribe((contributors) => {
this.contributors = contributors;
});
}
ngOnDestroy() {

View File

@@ -40,6 +40,10 @@
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/v1/fees/mempool-blocks" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/v1/fees/mempool-blocks</a></td>
<td i18n="api-docs.fees.mempool-blocks|API Docs for /api/v1/fees/mempool-blocks">Returns current mempool as projected blocks.</td>
</tr>
<tr>
<td class="nowrap"><a href="{{ network.val === '' ? '' : '/' + network.val }}/api/v1/cpfp/TXID" target="_blank">GET {{ network.val === '' ? '' : '/' + network.val }}/api/v1/cpfp/:txid</a></td>
<td i18n="api-docs.fees.cpfp|API Docs for /api/v1/fees/cpfp">Returns the ancestors and the best descendant fees for a transaction.</td>
</tr>
</table>
</ng-template>

View File

@@ -32,7 +32,6 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
) { }
ngOnInit(): void {
const showLegend = !this.isMobile && this.showLegend;
let labelHops = !this.showLegend ? 48 : 24;
if (this.small) {
labelHops = labelHops / 2;
@@ -73,28 +72,28 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
},
axisY: {
labelInterpolationFnc: (value: number): any => this.vbytesPipe.transform(value, 2),
offset: showLegend ? 160 : 60,
offset: this.showLegend ? 160 : 60,
},
plugins: this.inverted ? [Chartist.plugins.ctTargetLine({ value: 1000000 })] : []
};
if (showLegend) {
this.mempoolVsizeFeesOptions.plugins.push(
Chartist.plugins.legend({
legendNames: [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
if (this.showLegend) {
const legendNames: string[] = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
250, 300, 350, 400].map((sat, i, arr) => {
if (sat === 400) {
return '350+';
return '350+';
}
if (i === 0) {
if (this.stateService.network === 'liquid') {
return '0 - 1';
}
return '1 sat/vB';
return '0 - 1';
}
return arr[i - 1] + ' - ' + sat;
})
})
});
// Only Liquid has lower than 1 sat/vb transactions
if (this.stateService.network !== 'liquid') {
legendNames.shift();
}
this.mempoolVsizeFeesOptions.plugins.push(
Chartist.plugins.legend({ legendNames: legendNames })
);
}
}

View File

@@ -309,7 +309,7 @@ const defaultOptions = {
Chartist.plugins.legend = function (options: any) {
let cachedDOMPosition;
let cacheInactiveLegends: any = [];
let cacheInactiveLegends: { [key:number]: boolean } = {};
// Catch invalid options
if (options && options.position) {
if (!(options.position === 'top' || options.position === 'bottom' || options.position instanceof HTMLElement)) {
@@ -449,32 +449,29 @@ Chartist.plugins.legend = function (options: any) {
const legendIndex = parseInt(li.getAttribute('data-legend'));
const legend = legends[legendIndex];
if (!legend.active) {
legend.active = true;
li.classList.remove('inactive');
const activateLegend = (_legendIndex: number): void => {
legends[_legendIndex].active = true;
legendElement.childNodes[_legendIndex].classList.remove('inactive');
var indexOfInactiveLegend = cacheInactiveLegends.indexOf(legendIndex, 0)
if (indexOfInactiveLegend > -1) {
cacheInactiveLegends.splice(indexOfInactiveLegend, 1);
}
cacheInactiveLegends[_legendIndex] = false;
}
} else {
legend.active = false;
li.classList.add('inactive');
cacheInactiveLegends.push(legendIndex);
const deactivateLegend = (_legendIndex: number): void => {
legends[_legendIndex].active = false;
legendElement.childNodes[_legendIndex].classList.add('inactive');
cacheInactiveLegends[_legendIndex] = true;
}
const activeCount = legends.filter(function(legend: any) { return legend.active; }).length;
if (!options.removeAll && activeCount == 0) {
// If we can't disable all series at the same time, let's
// reenable all of them:
for (let i = 0; i < legends.length; i++) {
legends[i].active = true;
legendElement.childNodes[i].classList.remove('inactive');
}
cacheInactiveLegends = [];
for (let i = legends.length - 1; i >= 0; i--) {
if (i >= legendIndex) {
if (!legend.active) activateLegend(i);
} else {
if (legend.active) deactivateLegend(i);
}
}
// Make sure all values are undefined (falsy) when clicking the first legend
// After clicking the first legend all indices should be falsy
if (legendIndex === 0) cacheInactiveLegends = {};
const newSeries = [];
const newLabels = [];
@@ -512,7 +509,8 @@ Chartist.plugins.legend = function (options: any) {
const legendSeries = legend.series || [i];
const li = createNameElement(i, legendText, classNamesViable);
const isActive: boolean = !(cacheInactiveLegends.indexOf(i) > -1);
// If the value is undefined or false, isActive is true
const isActive: boolean = !cacheInactiveLegends[i];
if (isActive) {
activeSeries.push(seriesMetadata[i].data);
activeLabels.push(seriesMetadata[i].label);

View File

@@ -1,16 +1,19 @@
<div class="container-xl">
<div class="text-center">
<br />
<img src="./resources/mempool-logo-bigger.png" height="67.5" width="251">
<img [src]="officialMempoolSpace ? './resources/mempool-space-logo-bigger.png' : './resources/mempool-logo-bigger.png'" style="width: 250px;height:63px;">
<br /><br />
<h2>Terms of Service</h2>
<h6>Updated: October 12, 2020</h6>
<h6>Updated: March 09, 2021</h6>
<br><br>
<div class="text-left">
<p *ngIf="officialMempoolSpace">The official <a href="https://mempool.space/">mempool.space</a> website and <a href="https://mempool.space/api">its API service</a> are operated by Mempool Space K.K.</p>
<p *ngIf="!officialMempoolSpace">This website and its API service are operated by an individual member of the Bitcoin community, who is not affiliated with Mempool Space K.K.</p>
<h5>By using this website or accessing its API, you agree to these Terms of Service:</h5>
<br>
@@ -29,16 +32,44 @@
<h4>PRIVACY POLICY</h4>
<p>The operators of this website may collect your IP address as part of basic webserver logs and a self-hosted statistics application, but this data will never be sold or shared with third parties. Please use Tor when accessing this website to protect your privacy.</p>
<p>Out of respect for the Bitcoin community, this website does not utilize any third-party trackers or cookies, and the operators of this website do not share any user data with third-parties. However, your IP address may be collected as part of basic webserver logs necessary for systems administration purposes, and as part of an anonymized self-hosted statistics application for analytics. To protect your privacy, use Tor browser to access this website's Tor V3 onion hostname to conceal your IP address, or alternatively install and use your own self-hosted instance of this website.</p>
<h4>SPONSOR POLICY</h4>
<br>
<p>If you donate at least 0.01 BTC to mempool.space and submit your Twitter username at the time of donation, we will generally feature your Twitter profile photo on our About page with a link to your Twitter account. However, we reserve the right to remove sponsors listed on our website that in our sole discretion are inappropriate. All donations made are non-refundable.</p>
<h4>BITCOIN ONLY POLICY</h4>
</div>
<p>Out of respect for the Bitcoin community, this website does not support altcoins, and is generally "Bitcoin Only". The <a href="https://mempool.space/liquid/assets">IOU tokens for Bitcoin and fiat currencies on the Liquid network</a>, and the <a href="https://mempool.space/bisq/stats">BSQ colored Bitcoin for DAO governance on the Bisq Network</a>, are not considered altcoins as they do not distract users from Bitcoin and are part of our stated mission to support the entire Bitcoin ecosystem including Bitcoin Layer 2 Networks.</p>
<br><br>
<br>
<h4>EXTERNAL LINK POLICY</h4>
<p>Out of respect for the Bitcoin community, this website does not display advertising, and generally does not link to external websites. However, exceptions are made for references to Bitcoin's technical documentation, for acknowledgements of individual members and open-source projects within the Bitcoin community, and for direct contributors and supporters of <a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a>, as follows:</p>
<ul>
<li>In order to refer to Bitcoin technical documentation, external links may by added where applicable, to informational websites that respect that Bitcoin community and do not display advertising, altcoins, or third-party trackers, such as <a href="https://en.bitcoin.it/">bitcoin.it</a> and <a href="https://bitcoin.org/">bitcoin.org</a>.</li>
<br>
<li>In order to acknowledge individual contributors to <a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a> who have made <a href="https://github.com/mempool/mempool/graphs/contributors">at least 10 contributions visible on GitHub</a>, the contributor's GitHub profile photo will automatically be displayed on <a href="https://mempool.space/about">mempool.space/about</a>, with <u>a link to their GitHub profile</u>.</li>
<br>
<li>In order to acknowledge members of the Bitcoin community who have made a one-time donation of at least 0.01 BTC to <a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a> on <a href="https://mempool.space/about">mempool.space/about</a>, the Bitcoin community members's Twitter profile photo will automatically be displayed on <a href="https://mempool.space/about">mempool.space/about</a>, with <u>a link to their Twitter profile</u>.</li>
<br>
<li>In order to acknowledge members of the Bitcoin community who have made recurring donations of at least $100 USD per month to <a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a> through <a href="https://github.com/sponsors/mempool">our GitHub Sponsors program</a> for at least 6 months, the Bitcoin community member's GitHub profile photo will automatically be displayed on <a href="https://mempool.space/about">mempool.space/about</a>, with <u>a link to their GitHub profile</u>.</li>
<br>
<li>In order to acknowledge open source projects that benefit the Bitcoin community and have integrated <a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a> into their software, the open source project's logo may be displayed on <a href="https://mempool.space/about">mempool.space/about</a>, with <u>a link to the project's GitHub repository</u>.</li>
<br>
<li>In order to acknowledge federations or alliances within the Bitcoin community that have a relationship to <a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a>, the federation or alliance logo may be displayed on <a href="https://mempool.space/about">mempool.space/about</a>, with <u>a link to the alliance website</u>.</li>
<br>
<li>In order to acknowledge the enterprise organizations within the Bitcoin community that have made a significant one-time donation of at least $25,000 USD to <a href="https://github.com/mempool/mempool">The Mempool Open Source Project</a>, the organization's logo may be displayed on <a href="https://mempool.space/about">mempool.space/about</a>, with <u>a link to the organization's website</u>.</li>
</ul>
<p>However, we reserve the right to remove any links from our website that in our sole discretion are inappropriate. All donations are non-refundable.</p>
<br>
<p>EOF</p>
</div>
</div>
<br><br>
</div>

View File

@@ -1,9 +1,14 @@
import { Component } from '@angular/core';
import { Env, StateService } from '../../services/state.service';
@Component({
selector: 'app-terms-of-service',
templateUrl: './terms-of-service.component.html'
})
export class TermsOfServiceComponent {
constructor() { }
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
constructor(
private stateService: StateService,
) { }
}

View File

@@ -73,22 +73,7 @@
</table>
</div>
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
<td>{{ tx.fee | number }} <span i18n="transaction.fee.sat|Transaction Fee sat">sat</span> (<app-fiat [value]="tx.fee"></app-fiat>)</td>
</tr>
<tr>
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
<td>
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
&nbsp;
<app-tx-fee-rating *ngIf="tx.fee" [tx]="tx"></app-tx-fee-rating>
</td>
</tr>
</tbody>
</table>
<ng-container *ngTemplateOutlet="feeTable"></ng-container>
</div>
</div>
@@ -146,18 +131,7 @@
</table>
</div>
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width" i18n="transaction.fee|Transaction Fee">Fee</td>
<td>{{ tx.fee | number }} <span i18n="transaction.fee.sat|Transaction Fee sat">sat</span> (<app-fiat [value]="tx.fee"></app-fiat>)</td>
</tr>
<tr>
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
<td>{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
</tr>
</tbody>
</table>
<ng-container *ngTemplateOutlet="feeTable"></ng-container>
</div>
</div>
</div>
@@ -295,4 +269,36 @@
<ng-template let-i #nextBlockEta i18n="mempool-blocks.eta-of-next-block|Block Frequency">In ~{{ i }} minute</ng-template>
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} block</ng-template>
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} blocks</ng-template>
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} blocks</ng-template>
<ng-template #feeTable>
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
<td>{{ tx.fee | number }} <span i18n="transaction.fee.sat|Transaction Fee sat">sat</span> (<app-fiat [value]="tx.fee"></app-fiat>)</td>
</tr>
<tr>
<td i18n="transaction.fee-per-vbyte|Transaction fee">Fee per vByte</td>
<td>
{{ tx.fee / (tx.weight / 4) | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
<ng-template [ngIf]="tx.status.confirmed">
&nbsp;
<app-tx-fee-rating *ngIf="tx.fee && (!tx.effectiveFeePerVsize || tx.effectiveFeePerVsize === tx.fee / (tx.weight / 4))" [tx]="tx"></app-tx-fee-rating>
</ng-template>
</td>
</tr>
<tr *ngIf="tx.effectiveFeePerVsize && tx.effectiveFeePerVsize !== tx.fee / (tx.weight / 4)">
<td i18n="transaction.effective-fee|Effective transaction fee">Effective fee</td>
<td>
{{ tx.effectiveFeePerVsize | number : '1.1-1' }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
<ng-template [ngIf]="tx.status.confirmed">
&nbsp;
<app-tx-fee-rating *ngIf="tx.fee" [tx]="tx"></app-tx-fee-rating>
</ng-template>
</td>
</tr>
</tbody>
</table>
</ng-template>

View File

@@ -91,10 +91,28 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.getTransactionTime();
}
}
if (this.tx.status.confirmed) {
this.stateService.markBlock$.next({ blockHeight: tx.status.block_height });
} else {
this.stateService.markBlock$.next({ txFeePerVSize: tx.fee / (tx.weight / 4) });
if (tx.effectiveFeePerVsize) {
this.stateService.markBlock$.next({ txFeePerVSize: tx.effectiveFeePerVsize });
} else {
this.apiService.getCpfpinfo$(this.tx.txid)
.subscribe((cpfpInfo) => {
let totalWeight = tx.weight + cpfpInfo.ancestors.reduce((prev, val) => prev + val.weight, 0);
let totalFees = tx.fee + cpfpInfo.ancestors.reduce((prev, val) => prev + val.fee, 0);
if (cpfpInfo.bestDescendant) {
totalWeight += cpfpInfo.bestDescendant.weight;
totalFees += cpfpInfo.bestDescendant.fee;
}
const effectiveFeePerVsize = totalFees / (totalWeight / 4);
this.tx.effectiveFeePerVsize = effectiveFeePerVsize;
this.stateService.markBlock$.next({ txFeePerVSize: effectiveFeePerVsize });
});
}
}
},
(error) => {
@@ -139,7 +157,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
return;
}
const txFeePerVSize = this.tx.fee / (this.tx.weight / 4);
const txFeePerVSize = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
for (const block of mempoolBlocks) {
for (let i = 0; i < block.feeRange.length - 1; i++) {

View File

@@ -1,3 +1,3 @@
<span *ngIf="feeRating === 1" class="badge badge-success" i18n="tx-fee-rating.optimal|TX Fee Rating is Optimal">Optimal</span>
<span *ngIf="feeRating === 2" class="badge badge-warning" placement="bottom" i18n-ngbTooltip="tx-fee-rating.warning-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.warning|TX Fee Rating is Warning">Overpaid {{ overpaidTimes }}x</span>
<span *ngIf="feeRating === 3" class="badge badge-danger" placement="bottom" i18n-ngbTooltip="tx-fee-rating.danger-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.danger|TX Fee Rating is Danger">Overpaid {{ overpaidTimes }}x</span>
<span *ngIf="feeRating === 2" class="badge badge-warning" placement="bottom" i18n-ngbTooltip="tx-fee-rating.warning-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded | number : '1.1-1' }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.warning|TX Fee Rating is Warning">Overpaid {{ overpaidTimes }}x</span>
<span *ngIf="feeRating === 3" class="badge badge-danger" placement="bottom" i18n-ngbTooltip="tx-fee-rating.danger-tooltip" ngbTooltip="Only ~{{ medianFeeNeeded | number : '1.1-1' }} sat/vB was needed to get into this block" i18n="tx-fee-rating.overpaid.danger|TX Fee Rating is Danger">Overpaid {{ overpaidTimes }}x</span>

View File

@@ -52,7 +52,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
}
calculateRatings(block: Block) {
const feePervByte = this.tx.fee / (this.tx.weight / 4);
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
this.medianFeeNeeded = block.medianFee;
// Block not filled

View File

@@ -11,6 +11,7 @@ export interface Transaction {
// Custom properties
firstSeen?: number;
effectiveFeePerVsize?: number;
}
export interface Recent {

View File

@@ -8,3 +8,20 @@ export interface OptimizedMempoolStats {
mempool_byte_weight: number;
vsizes: number[] | string[];
}
interface Ancestor {
txid: string;
weight: number;
fee: number;
}
interface BestDescendant {
txid: string;
weight: number;
fee: number;
}
export interface CpfpInfo {
ancestors: Ancestor[];
bestDescendant: BestDescendant | null;
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { CpfpInfo, OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { Observable } from 'rxjs';
import { StateService } from './state.service';
import { WebsocketResponse } from '../interfaces/websocket.interface';
@@ -77,6 +77,10 @@ export class ApiService {
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/donations');
}
getContributor$(): Observable<any[]> {
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/contributors');
}
checkDonation$(orderId: string): Observable<any[]> {
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/donations/check?order_id=' + orderId);
}
@@ -84,4 +88,8 @@ export class ApiService {
getInitData$(): Observable<WebsocketResponse> {
return this.httpClient.get<WebsocketResponse>(this.apiBaseUrl + this.apiBasePath + '/api/v1/init-data');
}
getCpfpinfo$(txid: string): Observable<CpfpInfo> {
return this.httpClient.get<CpfpInfo>(this.apiBaseUrl + this.apiBasePath + '/api/v1/cpfp/' + txid);
}
}

View File

@@ -20,6 +20,7 @@ export class AudioService {
this.isPlaying = true;
this.audio.src = '../../../resources/sounds/' + name + '.mp3';
this.audio.load();
this.audio.volume = 0.65; // 65% volume
this.audio.play().catch((e) => {
console.log('Play sound failed' + e);
});

View File

@@ -15,11 +15,11 @@ export class Hex2asciiPipe implements PipeTransform {
if (!hex) {
return '';
}
let str = '';
let bytes: number[] = [];
for (let i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
bytes.push(parseInt(hex.substr(i, 2), 16));
}
return str;
return new TextDecoder('utf8').decode(Uint8Array.from(bytes));
}
}